Accelerate the chart range switch, prefetch two list pages, calm two polls

placeholderData keeps the previous range painted while a health-chart
range switch fetches, paired with its truth condition: the container
dims and goes inert while isPlaceholderData is true, so the old range
cannot masquerade as the new one. The mood chart needs no pairing — its
range is a client-side slice over one static-key query and never drops
to a skeleton.

/checkups and /mood gain the dashboard-template RSC prefetch: the
primary list read moves into a shared lib read (route + wrapper consume
one query, so filter/order/wire shape cannot drift), the wrapper honours
the DASHBOARD_SSR_PREFETCH kill-switch, resolves identity through
getUnswitchedSession, JSON-round-trips the dehydrated state, and fails
soft to the bare client on every error path. Page-prefetch unit tests
pin the seeded keys and the fail-soft arms; the SSR hydration spec
gains an assertion per page.

The achievement-unlock poll drops to five minutes (in-app writes already
invalidate the cell; the poll only covers out-of-band ingest), and the
plan-proposal poll gains the advisor-style bounded attempt ceiling so an
open thread stops polling after its budget.
This commit is contained in:
Marc-André Bombeck committed 2026-08-13 19:06:56 +02:00
1 parent ac96e5e975
commit 61114fab90
31 files changed
+962 -326

No files matched your search

+13 -6
View File
@@ -14,7 +14,12 @@
* prose editor by design: the PATCH contract never accepts the encrypted
* free-text fields.
*/
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
useMutation,
useQuery,
useQueryClient,
type UseQueryOptions,
} from "@tanstack/react-query";
import { queryKeys } from "@/lib/query-keys";
import { apiDelete, apiGet, apiPatch } from "@/lib/api/api-fetch";
@@ -63,12 +68,14 @@ export function useCoachPlans(opts?: {
filter?: PlanFilter;
enabled?: boolean;
/**
* Optional poll interval (ms). The chat thread uses a slow poll because
* proposals land asynchronously (the memory-refresh worker runs after the
* turn); the management page reads once. TanStack pauses the interval in
* background tabs by default, so the poll never runs unwatched.
* Optional poll interval (ms), or TanStack's interval callback so a caller
* can bound its poll (the chat thread caps attempts — see
* `nextProposalPollInterval` in `plan-proposal-card.tsx`). Proposals land
* asynchronously (the memory-refresh worker runs after the turn); the
* management page reads once. TanStack pauses the interval in background
* tabs by default, so the poll never runs unwatched.
*/
refetchInterval?: number;
refetchInterval?: UseQueryOptions<CoachPlanDTO[]>["refetchInterval"];
}) {
const filter = opts?.filter;
return useQuery({