Skip to content

Redesign: Portfolio route — StateBoundary + skeletons, keep release/requeue actions intact #6511

Description

@JSONbored

Context

apps/loopover-miner-ui/src/routes/portfolio.tsx renders the local portfolio-queue dashboard as three components: PortfolioQueueView (status-count cards + per-repo table), PortfolioQueueActionsSection (the release/requeue table), and PortfolioPage (the Card shell that composes both, polling fetchPortfolioQueue via usePolledFetch/DEFAULT_POLL_INTERVAL_MS from apps/loopover-miner-ui/src/lib/use-polled-fetch.ts).

Today every loading/empty/error state in this file is a hand-written literal <p>, not a shared component:

  • PortfolioQueueView's result === null branch renders "Loading local portfolio queue…" as plain muted text.
  • Its error branch renders role="alert" text: Could not read the local portfolio queue: {result.error}.
  • Its empty branch renders "No queued work yet — the cards fill in once the miner enqueues its first portfolio item.".
  • PortfolioQueueActionsSection repeats the same three-branch pattern independently: "Loading actionable queue items…", its own role="alert" error text, and "No in-progress or completed items to release or requeue right now.".

There is no skeleton placeholder anywhere in this app. @loopover/ui-kit already ships a working Skeleton primitive at packages/loopover-ui-kit/src/components/skeleton.tsx (<div className={cn("animate-pulse rounded-md bg-primary/10", className)} />), exported at the stable subpath @loopover/ui-kit/components/skeletonapps/loopover-miner-ui/package.json already depends on "@loopover/ui-kit": ">=0.1.0 <2.0.0", and portfolio.tsx already imports Button/Card/Table straight from @loopover/ui-kit/components/* — but nothing under apps/loopover-miner-ui/src imports Skeleton today.

StateBoundary/LoadingState/ErrorState/EmptyState/Spinner exist today only app-locally at apps/loopover-ui/src/components/site/state-views.tsx. The #6244 UI-primitives audit (apps/loopover-ui/src/chat-ui-primitives-audit.md, landed via PR #6474) confirmed these are reusable as-is for chat, but that file itself imports lucide-react, sonner's toast, and apps/loopover-ui-local @/lib/utils / @/lib/api/request — none of which apps/loopover-miner-ui pulls in today. apps/loopover-miner-ui cannot import a sibling app's src/ directly; it only ever consumes shared code through @loopover/ui-kit (confirmed workspace pattern: both apps/loopover-ui and apps/loopover-miner-ui depend on the package, never on each other's source). So this route redesign has a real, load-bearing prerequisite: state-views.tsx's primitives need to be ported into @loopover/ui-kit proper first, by a separate issue.

Requirements

⚠️ Read this before starting. This issue is blocked on the issue that ports state-views.tsx's LoadingState/EmptyState/ErrorState/StateBoundary primitives out of apps/loopover-ui/src/components/site/state-views.tsx and into @loopover/ui-kit proper. Do not fork or copy state-views.tsx into apps/loopover-miner-ui as a local one-off shim to unblock yourself, and do not add an import that reaches into apps/loopover-ui/src from this app — that is not how this workspace is wired (apps/loopover-miner-ui only ever depends on @loopover/ui-kit, never on a sibling app's source tree). Confirm the port has landed and import the real component from @loopover/ui-kit before opening a PR for this issue.

  • Only these files may change: apps/loopover-miner-ui/src/routes/portfolio.tsx, apps/loopover-miner-ui/src/portfolio-queue.test.tsx, and apps/loopover-miner-ui/src/portfolio-queue-actions.test.tsx. Do not touch run-history.tsx, ledgers.tsx, index.tsx, or __root.tsx — those routes are separate, independently-scoped redesign issues using the same pattern.
  • Do not modify apps/loopover-miner-ui/src/lib/portfolio-queue.ts, apps/loopover-miner-ui/src/lib/portfolio-queue-actions.ts, or apps/loopover-miner-ui/src/lib/use-polled-fetch.ts. The fetchers, the POST /api/portfolio-queue/{release,requeue} wiring, and the poll cadence (DEFAULT_POLL_INTERVAL_MS) stay byte-for-byte as they are.
  • The Button elements in PortfolioQueueActionsSection (Release/Requeue) and their onClick/disabled={pending} wiring to onRelease/onRequeuereleaseItem/requeueItem must keep the exact same call sites and behavior. This restyle only changes the surrounding container/loading/empty/error chrome around those buttons — the buttons and the endpoints they hit are explicitly out of scope. A future issue wires chat's action-dispatch through these same buttons/endpoints; changing them here would break that plan.
  • Replace PortfolioQueueView's result === null branch and PortfolioQueueActionsSection's result === null branch with the ported StateBoundary/LoadingState component wrapping Skeleton placeholders shaped like the real content: 3 skeleton cards for the dl status cards and skeleton rows for the per-repo table (in PortfolioQueueView); skeleton rows shaped like the queue-actions table (in PortfolioQueueActionsSection).
  • Replace the hand-written error/empty <p> blocks in both components with the ported ErrorState/EmptyState, preserving the exact current copy and role="alert" semantics on errors — do not reword the user-visible strings (Could not read the local portfolio queue: {error}, Could not read actionable queue items: {error}, Queue action failed: {error}, No queued work yet…, No in-progress or completed items to release or requeue right now.).
  • Update, don't delete, the two existing loading-state test assertions that will break under the new markup: apps/loopover-miner-ui/src/portfolio-queue.test.tsx's "renders the loading state before the first result arrives" test (currently expect(screen.getByText(/Loading local portfolio queue/i)).toBeTruthy()) and the equivalently-named test in apps/loopover-miner-ui/src/portfolio-queue-actions.test.tsx. Every other existing test in both files — including the release/requeue wiring assertions, the pending-disable test, and the #6090 regression test (failing release renders the error without a false re-fetch) — must keep passing with their existing behavioral assertions unchanged.

Deliverables

  • PortfolioQueueView's loading branch renders StateBoundary/LoadingState + Skeleton placeholders shaped like the 3 status cards and per-repo table
  • PortfolioQueueActionsSection's loading branch renders StateBoundary/LoadingState + Skeleton placeholders shaped like the queue-actions table
  • Both components' error/empty branches use the ported ErrorState/EmptyState, with unchanged copy and role="alert" on errors
  • lib/portfolio-queue.ts, lib/portfolio-queue-actions.ts, lib/use-polled-fetch.ts, and all Button/onClick wiring left untouched
  • portfolio-queue.test.tsx and portfolio-queue-actions.test.tsx updated so their loading-state assertions match the new markup, with all other existing assertions still passing unmodified

Test Coverage Requirements

apps/loopover-miner-ui sits under apps/**, which codecov.yml's ignore: list explicitly excludes (Codecov only collects coverage over src/**, packages/loopover-engine/src/**, and packages/loopover-miner/lib/**), so this PR is not scored by codecov/patch. It is still gated by this app's own local test script (apps/loopover-miner-ui/package.json's "test": "vitest run --coverage") as part of the whole-repo npm run test:ci local gate, which must stay green.

  • Update the two named loading-state test assertions to check for the new skeleton/StateBoundary output (e.g. by role/test-id on the skeleton container) instead of the removed literal text.
  • Keep every other existing assertion in portfolio-queue.test.tsx and portfolio-queue-actions.test.tsx passing unchanged, in particular: the release/requeue POST-wiring tests, the pending-disable test, and the #6090 regression test — these prove the buttons/actions genuinely weren't touched.
  • No new invariant tests are required beyond the above — this is a rendering-only change with no new branches/logic to cover.

Expected Outcome

The portfolio route shows content-shaped skeleton placeholders during its poll-cadence loading window instead of a flat "Loading…" sentence, using the same StateBoundary/Skeleton primitives the rest of the miner-ui redesign is standardizing on — while the release/requeue actions behave identically to today: same buttons, same POST /api/portfolio-queue/{release,requeue} endpoints, same disabled-while-pending behavior, verified by the untouched wiring tests. No new attack surface is introduced: no fetcher, endpoint, or button semantic changes.

Links & Resources

  • apps/loopover-miner-ui/src/routes/portfolio.tsx — the file being restyled
  • apps/loopover-miner-ui/src/lib/portfolio-queue-actions.ts — release/requeue wiring, untouched
  • apps/loopover-miner-ui/src/lib/portfolio-queue.ts — summary fetcher, untouched
  • apps/loopover-miner-ui/src/lib/use-polled-fetch.ts — poll cadence, untouched
  • apps/loopover-miner-ui/src/portfolio-queue.test.tsx and apps/loopover-miner-ui/src/portfolio-queue-actions.test.tsx — existing tests to update
  • packages/loopover-ui-kit/src/components/skeleton.tsx — the Skeleton primitive, already available today via @loopover/ui-kit/components/skeleton
  • apps/loopover-ui/src/components/site/state-views.tsx — the LoadingState/EmptyState/ErrorState/StateBoundary source to be ported
  • apps/loopover-ui/src/chat-ui-primitives-audit.md (landed via PR docs(ui): audit ui-kit for existing chat-adjacent UI primitives #6474) — confirms these primitives are reusable as-is once ported
  • The issue that ports state-views.tsx's primitives into @loopover/ui-kit — hard prerequisite; do not start this issue until that one has landed
  • The analogous redesign issues for the run-history and ledgers routes — same StateBoundary/Skeleton pattern applied to those routes, independently scoped
  • The portfolio release/requeue chat action-dispatch issue — later work that calls through these exact same, unmodified buttons and endpoints; this issue's job is to keep that surface visually restyled but behaviorally frozen underneath

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:featureGittensor-scored feature linked to a feature issue — scores a 0.25x multiplier.help wantedExtra attention is needed

    Projects

    Status
    Done

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions