fix(mood-chart): align queryKey to the shared mood-analytics factory entry

Pre-fix the dashboard mounted two separate consumers against the same
`/api/mood/analytics` endpoint: `app/page.tsx:291` keyed on
`queryKeys.moodAnalytics()` (`["mood-analytics"]`) and the chart at
`mood-chart.tsx:316` keyed on a bare `["mood-chart-data"]` literal.
TanStack treats different keys as separate cache slots, so a cold
dashboard mount fired two requests against the same endpoint — wasting
one HTTP round trip, one pg pool slot, and ~22 KB of payload per cold
mount (empirical-trace #3). Re-key the chart to `queryKeys.moodAnalytics()`
so TanStack dedups the request; align the chart's `staleTime` to the
dashboard's 60 s window so a route swing back to the dashboard within
a minute stays a free cache hit.

The chart's own subscription becomes the cache writer for any insights
/ mood sub-page that mounts it without the dashboard parent having
fetched first. No payload-shape change — the chart already typed its
response as `MoodAnalyticsData` which matches the dashboard's
`{ entries, summary }` shape byte-for-byte.
This commit is contained in:
Marc-André Bombeck committed 2026-05-21 11:45:25 +02:00
1 parent 1bcaae4764
commit 1dd1a9a7a5
1 file changed
+18 -1
+18 -1
View File
@@ -2,6 +2,7 @@
import { useQuery } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth";
import { queryKeys } from "@/lib/query-keys";
import {
ResponsiveContainer,
ComposedChart,
@@ -312,8 +313,22 @@ export function MoodChart({
const displayTitle = title ?? t("charts.mood");
// v1.4.40 W-RSC — align the chart's queryKey with the dashboard's
// `useQuery({ queryKey: queryKeys.moodAnalytics() })` call against the
// same `/api/mood/analytics` endpoint. Pre-fix the chart fetched its
// own copy under `["mood-chart-data"]` (audit C1) — TanStack treated
// the two keys as separate cache slots and the cold-mount dashboard
// fired the request twice (once at `app/page.tsx:291`, once here on
// the chart's first render). The dedup eliminates a ~22 KB round-trip
// and frees one pg pool slot per cold mount.
//
// `staleTime: 60_000` matches the dashboard's `DASHBOARD_QUERY_OPTS`
// so a route swing back to the dashboard within a minute keeps the
// cache fresh; the chart's own subscription becomes the cache writer
// for any insights / mood sub-page that mounts the chart without the
// dashboard parent having fetched first.
const { data, isLoading } = useQuery({
queryKey: ["mood-chart-data"],
queryKey: queryKeys.moodAnalytics(),
queryFn: async (): Promise<MoodAnalyticsData> => {
const res = await fetch("/api/mood/analytics");
if (!res.ok) throw new Error("Failed to fetch mood analytics");
@@ -321,6 +336,8 @@ export function MoodChart({
return json.data as MoodAnalyticsData;
},
enabled: isAuthenticated,
staleTime: 60_000,
refetchOnWindowFocus: false,
});
const chartData = useMemo((): ChartDataPoint[] | undefined => {