feat(dashboard): redesign dashboard — site selector heading, sites gallery, fleet health panel#600
Conversation
… gallery, and fleet health panel Reworks the Dashboard's Overview section into two modes driven by the site selection: - All Sites: a bare fleet-health metrics list (no card/health bar) followed by a horizontally-sliding Sites gallery of per-site cards (hashrate, power, temperature, health bar, needs-attention badge). - Single site: a Fleet health module with the health metrics + HealthBar + legend, and a Buildings / Racks / Components selector below. Buildings and Racks render as an overflowing, animated card gallery; Components renders the FleetErrors breakdown. Per-tab "View all" links to the matching fleet pages. Other changes: - Render a heading-style site selector at the top of the dashboard and hide the global topbar SitePicker on the dashboard route. - Add a `triggerClassName` prop to SitePicker for the heading variant. - Add a `compact` variant to the shared Metric component. - Add a `showMetrics` prop to RackCard for a simplified (no telemetry footer) card used by the dashboard; the fleet page keeps the full card. - Component error cards use the contrasting surface + always-critical icon bg. - Move the legacy FleetHealth card from features/dashboard to features/groupManagement (its only remaining consumer). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🔐 Codex Security Review
Review SummaryOverall Risk: NONE FindingsNo findings. Notes
Generated by Codex Security Review | |
There was a problem hiding this comment.
Pull request overview
This PR updates the ProtoFleet dashboard UX by replacing the old “Overview” layout with a heading-style site selector, a new fleet health module (single-site vs all-sites modes), and an all-sites “Sites” card gallery, plus supporting component tweaks and test coverage.
Changes:
- Add new dashboard sections/components:
FleetHealthSection(single-site),FleetHealthMetrics(shared metrics tiles), andSitesSection+SiteCard(all-sites gallery). - Move/reuse fleet health UI in group management and adjust dashboard imports/exports accordingly.
- Add UI flexibility knobs (Metric
variant, RackCardshowMetrics) and introduce new unit tests/stories.
Reviewed changes
Copilot reviewed 24 out of 27 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| client/src/shared/components/Metric/Metric.tsx | Adds variant support (default/compact) and adjusts skeleton sizing/spacing. |
| client/src/protoFleet/features/kpis/components/ComponentErrors/ComponentErrors.tsx | Updates icon styling and card surface styling for component error tiles. |
| client/src/protoFleet/features/groupManagement/pages/GroupOverviewPage.tsx | Re-points FleetHealth import to the new groupManagement location. |
| client/src/protoFleet/features/groupManagement/components/FleetHealth/index.ts | New barrel export for groupManagement FleetHealth. |
| client/src/protoFleet/features/groupManagement/components/FleetHealth/FleetHealth.tsx | Updates ChartWidget import path to dashboard component. |
| client/src/protoFleet/features/groupManagement/components/FleetHealth/FleetHealth.test.tsx | Adds coverage for fleet health rendering, loading, pluralization, and links. |
| client/src/protoFleet/features/groupManagement/components/FleetHealth/FleetHealth.stories.tsx | Adds Storybook stories for fleet health states. |
| client/src/protoFleet/features/fleetManagement/components/RackCard/RackCard.tsx | Adds showMetrics toggle to optionally hide telemetry footer. |
| client/src/protoFleet/features/fleetManagement/components/RackCard/RackCard.test.tsx | Adds tests for showMetrics default/false behavior. |
| client/src/protoFleet/features/dashboard/pages/Dashboard.tsx | Reworks the dashboard header/overview area and adds fleet health + sites section logic. |
| client/src/protoFleet/features/dashboard/index.ts | Removes FleetHealth export from the dashboard barrel. |
| client/src/protoFleet/features/dashboard/components/SitesSection/SitesSection.tsx | New all-sites “Sites” section with responsive horizontal gallery + pagination. |
| client/src/protoFleet/features/dashboard/components/SitesSection/SitesSection.test.tsx | Tests gallery sizing, pagination behavior, and loading skeletons. |
| client/src/protoFleet/features/dashboard/components/SitesSection/SiteCard.tsx | New per-site card with polled stats, compact metrics, health bar, and deep links. |
| client/src/protoFleet/features/dashboard/components/SitesSection/SiteCard.test.tsx | Tests SiteCard links, displayed metrics, badge behavior, and loading skeletons. |
| client/src/protoFleet/features/dashboard/components/SitesSection/index.ts | New barrel export for SitesSection. |
| client/src/protoFleet/features/dashboard/components/FleetHealthSection/useCardCarousel.ts | New reusable carousel hook for card-track measurement and stepping. |
| client/src/protoFleet/features/dashboard/components/FleetHealthSection/SiteResourcePanel.tsx | New tabbed panel (Buildings/Racks/Components) with galleries and FleetErrors integration. |
| client/src/protoFleet/features/dashboard/components/FleetHealthSection/SiteResourcePanel.test.tsx | Tests tab switching, empty state, and site-scoped “View all” linking. |
| client/src/protoFleet/features/dashboard/components/FleetHealthSection/index.ts | New barrel export for FleetHealthSection. |
| client/src/protoFleet/features/dashboard/components/FleetHealthSection/FleetHealthSection.tsx | New single-site Fleet health section: performance subheading, metrics, health bar, legend, resources panel. |
| client/src/protoFleet/features/dashboard/components/FleetHealthSection/FleetHealthSection.test.tsx | Tests scoping, subheading construction, and loading fallbacks. |
| client/src/protoFleet/features/dashboard/components/FleetHealthMetrics/index.ts | New barrel export for FleetHealthMetrics (+ props type). |
| client/src/protoFleet/features/dashboard/components/FleetHealthMetrics/FleetHealthMetrics.tsx | New shared metric-tiles component for fleet health counts/percentages. |
| client/src/protoFleet/features/dashboard/components/FleetHealthMetrics/FleetHealthMetrics.test.tsx | Tests percentages, loading skeletons, em-dash state, and empty fleet handling. |
| client/src/protoFleet/components/PageHeader/SitePicker/SitePicker.tsx | Adds triggerClassName to support heading-style picker typography. |
| client/src/protoFleet/components/PageHeader/PageHeader.tsx | Hides the topbar SitePicker on the Dashboard route to avoid duplicate selectors. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c71b485ff9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…etry, e2e
- Viewport-gate each SiteCard's GetSiteStats poll (useInViewport) so the
all-sites gallery no longer fans out one poll per site every tick; only
on-screen cards poll. useSiteStats now keys scope on siteId only (matching
useBuildingStats) so suspend/resume keeps last-good stats without a flash.
- FleetHealthMetrics: label the healthy bucket "Healthy" (was "Health") to
match the legend and FleetHealth copy.
- Dashboard: surface ListSites failures through the heading SitePicker's
retry affordance instead of an indefinite loading skeleton.
- FleetHealthSection: key SiteResourcePanel by siteId so switching sites
remounts it and clears the previous site's building/rack cards.
- e2e: update dashboard.spec ("Overview" title -> "Sites"), and drop the two
issue-card navigation tests whose all-sites affordance moved to the
single-site Components tab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Revert the always-critical icon background — at 0 issues or while loading the icon now uses the neutral surface, so a healthy card no longer reads as an error state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 52eae992f2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Document that the index reset uses React's endorsed adjust-state-on-prop-change render pattern (converges, runs before paint) and why the useLayoutEffect alternative is intentionally avoided (repo react-hooks/set-state-in-effect rule). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 147e72c8d1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Swap the dashboard Buildings tab from BuildingSummaryCard (health bar) to the
floor-plan BuildingCard, and add a showMetrics prop to BuildingCard that omits
the telemetry footer — mirroring the simplified RackCard. The dashboard renders
showMetrics={false}; the fleet/sites pages keep the full card.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 32350ba338
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…ction to all-sites - Keep knownSiteIds unknown (not an empty loaded set) when ListSites errors, mirroring the picker, so a transient failure on /:site/dashboard no longer makes useActiveSite treat the route site as stale and fall back to all-sites. - Render the Sites gallery only when activeSite.kind === 'all'; on /unassigned/dashboard it no longer shows org-wide site cards while the rest of the page is scoped to unassigned miners. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ng flash - Buildings tab: record listBuildingsBySite failures and show an error/retry instead of collapsing to 'No buildings'; keep last-good rows. - Racks tab: surface useDeviceSetListState errors with a retry instead of a permanent skeleton. - Gate the racks loading state on the real fetch having started so switching to Racks no longer flashes 'No racks in this site yet.' from the stale matchNone state before the scoped fetch runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7eb47db9e3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…d CTAs Adds an optional `to` prop to the shared Button: when set it renders a react-router Link (an <a>) with the exact same styling, so navigation CTAs no longer nest a <button> inside a <Link> (invalid DOM / duplicate focus stops). Disabled link CTAs render an inert styled <span>. Swaps every dashboard CTA (SitesSection, SiteCard, SiteResourcePanel, FleetHealthSection) to <Button to=...>, removing the Link wrappers and styling duplication. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fd61aff67e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
When useComponentErrors' first request fails the counts stay undefined, so the FleetErrors grid showed permanent skeletons with no retry. Render an error/retry state (gated on the hook's error + !hasLoaded) before the grid, wired to the hook's refetch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewable diff: +887/-61 across 19 files (excludes generated, test, and story files).
Summary
Redesigns the Dashboard's Overview area into a site-aware layout. The page now carries its own heading-style site selector (the global topbar picker is hidden here), and the content below it changes shape with the selection: All Sites shows a flat fleet-health metric list plus a horizontally-sliding gallery of per-site cards; a single selected site shows a Fleet health module (metrics + health bar) with a Buildings / Racks / Components selector underneath. The Performance section and the rest of the page are unchanged.
How it works
Site selector as the page heading. The Dashboard renders the existing
SitePickerat the top of the Overview section using a new heading-size variant. Selecting a site sets the active site (store + route, exactly as the topbar picker does) and navigates to the scoped dashboard. Because the dashboard now owns a selector, the global topbarSitePickeris hidden whenever the current route resolves to/dashboard(covers/dashboard,/:site/dashboard,/unassigned/dashboard).All-Sites mode. Below the heading,
FleetHealthMetricsrenders the five fleet-health tiles (Your fleet / Health / Needs attention / Offline / Sleeping) as a bare list — no card chrome, no composition bar. Underneath,SitesSectionrenders oneSiteCardper site in a horizontal track that overflows the content width. The number of cards visible at once is responsive (desktop 3, laptop/tablet 2, phone 1); chevrons slide the track one card at a time and the offset is clamped so the last card right-aligns with the page content. EachSiteCardpollsGetSiteStatsand shows hashrate (auto-scaled units), power, an integer temperature range, a full-widthHealthBar, a "needs attention" badge linking to the site-scoped, status-filtered miner list, and an arrow link to the site detail page.Single-site mode.
FleetHealthSectionrenders a module card: a performance subheading built fromGetSiteStats(e.g.922.6 PH/s, 3.5 MW (29% of 12 MW), 3.8 J/TH, each metric gated on its own reporting count), the fleet-health metric tiles, a full-widthHealthBar+ legend, and below a divider,SiteResourcePanel. That panel uses the duration-selector button row to switch between Buildings, Racks, and Components:listBuildingsBySite→BuildingSummaryCardgallery.useDeviceSetListStatescoped to the site → simplifiedRackCards (telemetry footer hidden) linking to the rack detail page.useComponentErrorsfor the site → theFleetErrorsbreakdown.Each tab's data fetch is gated so only the active tab does work: Buildings fetches in an effect gated by the tab, Racks resolves to a
matchNonesite filter (which short-circuits the request insideuseDeviceSetListState) until selected, and Components passesenabled: tab === "Components". Buildings/Racks render in an overflowing, animated gallery driven by a shareduseCardCarouselhook; chevrons appear in the toolbar only when the row overflows. The gallery clips at the card's edges (so cards stay visible bleeding through the card padding as they slide) but the slide clamps against content width (so the last card lands flush with the content edge). A per-tab "View all" links to the matching site-scoped fleet page (Components → the needs-attention-filtered miner list).Diagrams
flowchart TD Picker["Site selector heading (SitePicker, heading-300)"] --> Mode{"activeSite.kind"} Mode -->|"all / unassigned"| AllMode["FleetHealthMetrics (bare metric list)"] AllMode --> Sites["SitesSection: animated SiteCard gallery"] Mode -->|"site"| SiteMode["FleetHealthSection module"] SiteMode --> Health["Metric tiles + HealthBar + legend"] SiteMode --> Panel["SiteResourcePanel"]flowchart LR Sel["Buildings / Racks / Components selector"] --> T{"active tab"} T -->|"Buildings"| B["listBuildingsBySite -> BuildingSummaryCard"] T -->|"Racks"| R["useDeviceSetListState (site-scoped) -> RackCard (showMetrics=false)"] T -->|"Components"| C["useComponentErrors -> FleetErrors"] B --> Car["useCardCarousel: overflow detect + chevron slide"] R --> CarAreas of the code involved
features/dashboard/pages/Dashboard.tsxFleetErrors/Overviewtitle from this pagefeatures/dashboard/components/FleetHealthMetrics/(new)features/dashboard/components/SitesSection/(new)SiteCardfeatures/dashboard/components/FleetHealthSection/(new)SiteResourcePanel,useCardCarouselcomponents/PageHeader/PageHeader.tsxSitePickeron the dashboard routecomponents/PageHeader/SitePicker/SitePicker.tsxtriggerClassNameprop (default unchanged)features/fleetManagement/components/RackCard/RackCard.tsxshowMetricsprop (defaulttrue) to hide the telemetry footershared/components/Metric/Metric.tsxcompactvariant (smaller value, tighter gap)features/kpis/components/ComponentErrors/ComponentErrors.tsxfeatures/groupManagement/components/FleetHealth/(moved)features/dashboard;GroupOverviewPage+ dashboard barrel updatedKey technical decisions & trade-offs
useCardCarousel(ResizeObserver) because card widths are fixed and overflow is content-driven. Chose measurement there over a fixed count so arrows appear only on real overflow.useDeviceSetListStatebut stay mounted; an inactive tab returns amatchNonefilter that skips the network call, rather than conditionally mounting the hook. Keeps the carousel/toolbar state in one component at the cost of the hook staying mounted.Metric(compact variant, additive),RackCard(showMetrics, default true), andSitePicker(triggerClassName, default unchanged) are backward-compatible.ComponentErrorssurface/icon changes are not gated — they also changeFleetErrorsonGroupOverviewPage(intentional, for consistency).Testing & validation
FleetHealthMetrics,SitesSection,SiteCard,FleetHealthSection,SiteResourcePanel,RackCardvariant) and the existingFleetHealthtest moved with the component.tsc, ESLint, and Prettier are clean; the touched suites pass (237 tests across the affected dirs).dashboard.specstill asserts the old"Overview"section title and a Control Boards issue-card navigation step that no longer exist on this page — those e2e steps need updating before they go green.