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
apps/loopover-miner-ui/src/routes/ledgers.tsx (lines 60-266) renders the "Ledgers" route in two structurally separate pieces that currently share nothing but a copy-pasted loading/error JSX shape:
A read-only ledger summary: claims count cards, a governor-events CountTable, an events-by-type CountTable, and a recent-events Table, all sourced from a single GET /api/ledgers call via apps/loopover-miner-ui/src/lib/ledgers.ts.
A separate "Governor control" section: an Input + Button pair wired to POST /api/governor/{pause,resume} via apps/loopover-miner-ui/src/lib/governor.ts, with its own independent fetch/action loop (fetching current governor state, then submitting the pause/resume request) distinct from the ledger-summary fetch above.
Per the miner-ui audit, this route's loading state today is the literal string "Loading local ledgers…" (ledgers.tsx:118) — plain gray text, re-rendered on every 10s poll cycle, with no shimmer/placeholder shape — even though @loopover/ui-kit/components/skeleton.tsx (packages/loopover-ui-kit/src/components/skeleton.tsx) already ships in the shared package and is imported nowhere under apps/loopover-miner-ui/src. The same audit found this exact 4-branch loading/error/empty/success pattern hand-duplicated near-identically across all four routes (run-history.tsx, portfolio.tsx, ledgers.tsx, index.tsx) with no shared component factoring it out. This issue scopes onlyledgers.tsx; the other three routes are covered by separate sibling issues in the same visual/ui-kit polish track and are out of scope here.
The shared-design-system audit confirms apps/loopover-miner-ui already consumes @loopover/ui-kit first-class and directly (e.g. Button/Card/Table imported from @loopover/ui-kit/components/* in portfolio.tsx:4-6) — it never imports from apps/loopover-ui, which is a separate app with its own local src/components/* tree. The reusable LoadingState/EmptyState/ErrorState/StateBoundary primitives (plus a bare Spinner) that this issue needs currently live only at apps/loopover-ui/src/components/site/state-views.tsx — confirmed reusable as-is by the ui-kit chat-primitives audit (#6244, landed via PR #6474, merged 2026-07-16) — but that audit also established these primitives must first be ported from that app-local file into @loopover/ui-kit proper before any other app (including apps/loopover-miner-ui) can consume them. This issue is blocked on that port landing first — see the Requirements callout below.
This is a pure visual/restyle change with no new attack surface: the write-capable governor pause/resume request path (lib/governor.ts, the Button's click handler, and the underlying POST /api/governor/{pause,resume} call) is explicitly left untouched by this issue. Only the rendering layer — what's shown while those calls are pending, what's shown if they fail, and how the governor-control section is laid out — changes.
Requirements
⚠️ Read this before starting. StateBoundary, LoadingState, and ErrorState must be imported from @loopover/ui-kit once that package exports them (following the existing @loopover/ui-kit/components/<name> convention already used for Skeleton, Card, Table, etc. — e.g. @loopover/ui-kit/components/state-views). Do not import apps/loopover-ui/src/components/site/state-views.tsx directly from apps/loopover-miner-ui — these are separate apps with no cross-app dependency, and no other file in apps/loopover-miner-ui does this. Do not hand-roll a second local copy of LoadingState/ErrorState/StateBoundary inside apps/loopover-miner-ui as a workaround either. If @loopover/ui-kit does not yet export these primitives when you pick this up, this issue is still blocked — stop and say so in the issue/PR rather than reimplementing them locally; do not treat "the ui-kit export doesn't exist yet" as license to invent a parallel version.
The ledger-summary fetch (claims counts, both CountTables, the recent-events Table — everything currently gated by the "Loading local ledgers…" branch at ledgers.tsx:118 and its adjacent error branch) must be wrapped in StateBoundary, rendering LoadingState while pending and ErrorState on fetch failure.
The governor pause/resume fetch/action loop must be wrapped in its own, separate StateBoundary/LoadingState/ErrorState — it is a distinct async flow from the ledger-summary fetch above and must not be merged into a single shared boundary that would, for example, block the whole page on a governor-state fetch failure.
Skeleton placeholders (@loopover/ui-kit/components/skeleton) must replace the plain-text loading branches, shaped to approximate the final layout (count-card grid, table rows) rather than a single generic bar.
The "Governor control" section's layout must be restyled — reorganize the existing Input + Button pair's spacing/grouping/visual hierarchy using existing @loopover/ui-kit primitives and design tokens (the same tokens already in use elsewhere in this app, e.g. text-token-sm, rounded-token-sm, hover-surface). Do not introduce a new component library, new ad-hoc CSS, or new design tokens.
Do not modify apps/loopover-miner-ui/src/lib/ledgers.ts.
Do not modify apps/loopover-miner-ui/src/lib/governor.ts.
Do not modify the pause/resume Button's existing click handlers or its call into lib/governor.ts — the action logic itself is out of scope; only what wraps it visually changes. A PR that touches the pause/resume request logic, even incidentally, does not satisfy this issue.
This is a contributor-facing visual PR: include before/after screenshots (or a short screen recording) in the PR description showing, for both async flows independently: the skeleton loading state, the populated/success state, and an error state (e.g. via devtools request blocking or throttling). Per house convention, a visual PR without this evidence is closed rather than merged.
Deliverables
Ledger-summary section (claims cards, both CountTables, recent-events Table) wrapped in StateBoundary using ui-kit's LoadingState/ErrorState, replacing the current plain-text loading/error branches.
Skeleton placeholders shown during the ledger-summary LoadingState, shaped to the final card/table layout.
Governor pause/resume fetch/action loop wrapped in its own, independently-triggered StateBoundary/LoadingState/ErrorState.
Governor-control section visually restyled (layout/spacing/hierarchy only) using existing @loopover/ui-kit primitives and tokens.
git diff confirms zero changes to lib/ledgers.ts, lib/governor.ts, and the pause/resume Button's click-handler wiring.
Before/after screenshots (or recording) for both async flows' loading/success/error states included in the PR description.
Test Coverage Requirements
apps/loopover-miner-ui/** is outside src/**, so Codecov's patch-coverage gate (the 99%+ branch-counted requirement in CLAUDE.md) does not numerically gate this change — note this explicitly in the PR so a reviewer isn't confused about why no Codecov check appears. That does not relax the bar for actually verifying the change: if apps/loopover-miner-ui already has an existing test pattern for its routes (check for *.test.tsx alongside src/routes/*.tsx before starting), extend it to cover the new StateBoundary-driven loading/error/populated branches for both the ledger-summary and governor flows. If no such test harness exists yet for this app, say so explicitly in the PR rather than inventing a new one from scratch — the before/after screenshot requirement above is the mandatory verification either way, and stands regardless of what automated coverage exists.
Expected Outcome
The Ledgers route no longer shows raw "Loading local ledgers…" text on every poll cycle; loading is a Skeleton shaped to the eventual content.
Both the ledger-summary read and the governor pause/resume flow render through the shared StateBoundary/LoadingState/ErrorState primitives instead of hand-rolled inline JSX, matching the pattern the sibling route-redesign issues apply to run-history.tsx/portfolio.tsx/index.tsx.
The governor-control section reads as an organized, visually distinct panel instead of a bare Input+Button row.
lib/ledgers.ts, lib/governor.ts, and the pause/resume action wiring are byte-for-byte unchanged — verifiable directly from the diff — so there is exactly one write path into the governor (the existing one) both before and after this PR.
Links & Resources
apps/loopover-miner-ui/src/routes/ledgers.tsx (lines 60-266) — the file this issue touches.
apps/loopover-miner-ui/src/lib/ledgers.ts — untouched; GET /api/ledgers fetcher.
apps/loopover-miner-ui/src/lib/governor.ts — untouched; POST /api/governor/{pause,resume} actions.
packages/loopover-ui-kit/src/components/skeleton.tsx — existing, currently-unused Skeleton component this issue adopts.
apps/loopover-miner-ui/src/routes/portfolio.tsx:4-6 — existing precedent for importing shared components directly from @loopover/ui-kit/components/*.
Sibling issues in the same visual/ui-kit polish track (out of scope here, reference only): the Overview route redesign issue, the Run history route redesign issue, and the Portfolio route redesign issue — each applies the same StateBoundary/Skeleton adoption pattern to its own route.
The broader miner dashboard redesign epic (routed dashboard + persistent chat rail) this issue is part of — see that epic for the full split between this visual/ui-kit polish track and the separately-scoped, higher-safety-scope chat track.
Context
apps/loopover-miner-ui/src/routes/ledgers.tsx(lines 60-266) renders the "Ledgers" route in two structurally separate pieces that currently share nothing but a copy-pasted loading/error JSX shape:CountTable, an events-by-typeCountTable, and a recent-eventsTable, all sourced from a singleGET /api/ledgerscall viaapps/loopover-miner-ui/src/lib/ledgers.ts.Input+Buttonpair wired toPOST /api/governor/{pause,resume}viaapps/loopover-miner-ui/src/lib/governor.ts, with its own independent fetch/action loop (fetching current governor state, then submitting the pause/resume request) distinct from the ledger-summary fetch above.Per the miner-ui audit, this route's loading state today is the literal string
"Loading local ledgers…"(ledgers.tsx:118) — plain gray text, re-rendered on every 10s poll cycle, with no shimmer/placeholder shape — even though@loopover/ui-kit/components/skeleton.tsx(packages/loopover-ui-kit/src/components/skeleton.tsx) already ships in the shared package and is imported nowhere underapps/loopover-miner-ui/src. The same audit found this exact 4-branch loading/error/empty/success pattern hand-duplicated near-identically across all four routes (run-history.tsx,portfolio.tsx,ledgers.tsx,index.tsx) with no shared component factoring it out. This issue scopes onlyledgers.tsx; the other three routes are covered by separate sibling issues in the same visual/ui-kit polish track and are out of scope here.The shared-design-system audit confirms
apps/loopover-miner-uialready consumes@loopover/ui-kitfirst-class and directly (e.g.Button/Card/Tableimported from@loopover/ui-kit/components/*inportfolio.tsx:4-6) — it never imports fromapps/loopover-ui, which is a separate app with its own localsrc/components/*tree. The reusableLoadingState/EmptyState/ErrorState/StateBoundaryprimitives (plus a bareSpinner) that this issue needs currently live only atapps/loopover-ui/src/components/site/state-views.tsx— confirmed reusable as-is by the ui-kit chat-primitives audit (#6244, landed via PR #6474, merged 2026-07-16) — but that audit also established these primitives must first be ported from that app-local file into@loopover/ui-kitproper before any other app (includingapps/loopover-miner-ui) can consume them. This issue is blocked on that port landing first — see the Requirements callout below.This is a pure visual/restyle change with no new attack surface: the write-capable governor pause/resume request path (
lib/governor.ts, theButton's click handler, and the underlyingPOST /api/governor/{pause,resume}call) is explicitly left untouched by this issue. Only the rendering layer — what's shown while those calls are pending, what's shown if they fail, and how the governor-control section is laid out — changes.Requirements
CountTables, the recent-eventsTable— everything currently gated by the"Loading local ledgers…"branch at ledgers.tsx:118 and its adjacent error branch) must be wrapped inStateBoundary, renderingLoadingStatewhile pending andErrorStateon fetch failure.StateBoundary/LoadingState/ErrorState— it is a distinct async flow from the ledger-summary fetch above and must not be merged into a single shared boundary that would, for example, block the whole page on a governor-state fetch failure.Skeletonplaceholders (@loopover/ui-kit/components/skeleton) must replace the plain-text loading branches, shaped to approximate the final layout (count-card grid, table rows) rather than a single generic bar.Input+Buttonpair's spacing/grouping/visual hierarchy using existing@loopover/ui-kitprimitives and design tokens (the same tokens already in use elsewhere in this app, e.g.text-token-sm,rounded-token-sm,hover-surface). Do not introduce a new component library, new ad-hoc CSS, or new design tokens.apps/loopover-miner-ui/src/lib/ledgers.ts.apps/loopover-miner-ui/src/lib/governor.ts.Button's existing click handlers or its call intolib/governor.ts— the action logic itself is out of scope; only what wraps it visually changes. A PR that touches the pause/resume request logic, even incidentally, does not satisfy this issue.Deliverables
CountTables, recent-eventsTable) wrapped inStateBoundaryusing ui-kit'sLoadingState/ErrorState, replacing the current plain-text loading/error branches.Skeletonplaceholders shown during the ledger-summaryLoadingState, shaped to the final card/table layout.StateBoundary/LoadingState/ErrorState.@loopover/ui-kitprimitives and tokens.git diffconfirms zero changes tolib/ledgers.ts,lib/governor.ts, and the pause/resumeButton's click-handler wiring.Test Coverage Requirements
apps/loopover-miner-ui/**is outsidesrc/**, so Codecov's patch-coverage gate (the 99%+ branch-counted requirement in CLAUDE.md) does not numerically gate this change — note this explicitly in the PR so a reviewer isn't confused about why no Codecov check appears. That does not relax the bar for actually verifying the change: ifapps/loopover-miner-uialready has an existing test pattern for its routes (check for*.test.tsxalongsidesrc/routes/*.tsxbefore starting), extend it to cover the newStateBoundary-driven loading/error/populated branches for both the ledger-summary and governor flows. If no such test harness exists yet for this app, say so explicitly in the PR rather than inventing a new one from scratch — the before/after screenshot requirement above is the mandatory verification either way, and stands regardless of what automated coverage exists.Expected Outcome
"Loading local ledgers…"text on every poll cycle; loading is aSkeletonshaped to the eventual content.StateBoundary/LoadingState/ErrorStateprimitives instead of hand-rolled inline JSX, matching the pattern the sibling route-redesign issues apply torun-history.tsx/portfolio.tsx/index.tsx.Input+Buttonrow.lib/ledgers.ts,lib/governor.ts, and the pause/resume action wiring are byte-for-byte unchanged — verifiable directly from the diff — so there is exactly one write path into the governor (the existing one) both before and after this PR.Links & Resources
apps/loopover-miner-ui/src/routes/ledgers.tsx(lines 60-266) — the file this issue touches.apps/loopover-miner-ui/src/lib/ledgers.ts— untouched;GET /api/ledgersfetcher.apps/loopover-miner-ui/src/lib/governor.ts— untouched;POST /api/governor/{pause,resume}actions.packages/loopover-ui-kit/src/components/skeleton.tsx— existing, currently-unusedSkeletoncomponent this issue adopts.apps/loopover-ui/src/components/site/state-views.tsx— source ofLoadingState/EmptyState/ErrorState/StateBoundary, confirmed reusable as-is by research: audit ui-kit for existing message/chat-adjacent UI primitives #6244 (PR docs(ui): audit ui-kit for existing chat-adjacent UI primitives #6474, merged 2026-07-16); this issue is blocked on the separate issue that ports these primitives from this app-local file into@loopover/ui-kitproper.apps/loopover-miner-ui/src/routes/portfolio.tsx:4-6— existing precedent for importing shared components directly from@loopover/ui-kit/components/*.StateBoundary/Skeletonadoption pattern to its own route.