fix(insights): stop the pulse chart from faking a continuous shape, add honest error states

Intraday pulse rendered sparse spot readings as a smooth interpolated
curve across hours-wide gaps, implying continuous monitoring the data
never had. The chart now grids one point per bucket, breaks the area
over any missing bucket (connectNulls={false}), and discloses coverage
("based on N readings across M hours") whenever that produces a visible
break.

Four surfaces also collapsed a failed fetch into the honest empty/no-
data state instead of surfacing the error: the Today hero, the
medication efficacy tab (previously stuck on the loading skeleton
forever on error), the intraday pulse chart, and the recent-workouts
dashboard tile. All four now branch on isError and render
QueryErrorCard with a retry action; the tile's "loading…" text line is
replaced with row-shaped skeletons.
This commit is contained in:
Marc-André Bombeck committed 2026-07-17 23:50:06 +02:00
1 parent fb73c97125
commit d124a3239d
12 files changed
+176 -36

No files matched your search

+8 -2
View File
@@ -35,6 +35,7 @@ import dynamic from "next/dynamic";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { EmptyState } from "@/components/ui/empty-state";
import { QueryErrorCard } from "@/components/ui/query-error-card";
import { ChartErrorBoundary } from "@/components/charts/chart-error-state";
import { ChartSkeleton } from "@/components/charts/chart-skeleton";
import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic";
@@ -790,8 +791,11 @@ export default function DashboardPageClient() {
decision 1). Renders the S1 daily digest: the day's read, the
honest score/freshness face, and the worth-a-look rail. Shimmer
skeleton while the cached digest loads; a genuinely empty account
degrades to nothing (the hero itself returns null). The dense
tile grid + charts below are untouched. */}
degrades to nothing (the hero itself returns null). A FAILED fetch
is a distinct state from that honest empty degrade — it renders
`<QueryErrorCard>` with a retry action rather than silently
falling through to nothing. The dense tile grid + charts below
are untouched. */}
{insightsEnabled &&
// `!mounted` pins the SSR pass AND the first hydration render to the
// skeleton (v1.16.4 pattern): the digest query rehydrates from the
@@ -800,6 +804,8 @@ export default function DashboardPageClient() {
// skeleton — a text-content hydration mismatch (React #418).
(!mounted || digestQuery.isLoading ? (
<TodayHeroSkeleton />
) : digestQuery.isError ? (
<QueryErrorCard onRetry={() => digestQuery.refetch()} />
) : digestQuery.data ? (
<TodayHero digest={digestQuery.data} />
) : null)}