mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
Pair every daily-read invalidation with its inactive refetch
Ten client mutation sites forgot a consumer: a coach-accepted checkup stayed invisible for up to five minutes, a threshold or target edit left the dashboard bands stale, a score-config save marked the daily reads stale but never refetched them, a booked visit missed the Today rail, a source-priority reorder kept every chart on the old canonical picks, an adopted self-context note never reached the Settings read. Each site now invalidates what its write changed and forces the unmounted snapshot + digest reads to refetch through refetchInactiveDailyReads. New illnessDependentKeys bundle carries the daily reads (Rest Mode frames both), dailyDigest joins encounterDependentKeys, and the chart-data prefix gains a factory accessor for whole-cache flushes. The daily-reads guard learns the cycle / encounter / vaccination / illness bundle families and a second matcher for direct snapshot/digest invalidateQueries without a forced refetch, each with a frozen reasoned allowlist. Both matchers were proven live by reverting a fix and watching the guard go red (see the test doc comment).
This commit is contained in:
1 parent
002d120b69
commit
f2f40ae8b2
17 files changed
+239
-24
No files matched your search
@@ -70,7 +70,7 @@ import {
|
||||
withBaseToken,
|
||||
} from "@/lib/api/optimistic-token";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { queryKeys, refetchInactiveDailyReads } from "@/lib/query-keys";
|
||||
import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query";
|
||||
import {
|
||||
buildScoreConfigRows,
|
||||
@@ -210,12 +210,12 @@ export function ScoreSection() {
|
||||
queryKey: queryKeys.healthScoreConfig(),
|
||||
});
|
||||
// The number now means something else. Every surface that holds a
|
||||
// computed score has to go, or the change reads as broken.
|
||||
// computed score has to go, or the change reads as broken. The daily
|
||||
// reads are unmounted on the Settings page and run with
|
||||
// `refetchOnMount: false`, so a plain invalidation marks them stale
|
||||
// without ever refetching — force the inactive refetch instead.
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.analytics() });
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.dashboardSnapshot(),
|
||||
});
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.dailyDigest() });
|
||||
void refetchInactiveDailyReads(queryClient);
|
||||
toastWrittenOutcome("success", t("settings.sections.score.saved"));
|
||||
},
|
||||
onError: (err) => {
|
||||
|
||||
@@ -48,7 +48,7 @@ import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { SettingsCard } from "@/components/settings/settings-card";
|
||||
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { queryKeys, refetchInactiveDailyReads } from "@/lib/query-keys";
|
||||
import {
|
||||
DEFAULT_DEVICE_TYPE_PRIORITY,
|
||||
DEFAULT_SOURCE_PRIORITY,
|
||||
@@ -160,10 +160,15 @@ export function SourcesSection() {
|
||||
},
|
||||
onSuccess: (saved) => {
|
||||
queryClient.setQueryData(queryKeys.sourcePriority(), saved);
|
||||
// The analytics aggregator folds source priority into per-day
|
||||
// canonical-row picks — flush its cache so the charts re-paint
|
||||
// with the new picker on the next mount.
|
||||
// The analytics aggregator, the per-chart daily aggregates, the
|
||||
// measurement list and the dashboard snapshot all fold source
|
||||
// priority into their per-day canonical-row picks — flush them so
|
||||
// every chart and tile re-paints with the new picker, not only the
|
||||
// analytics report.
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.analytics() });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.chartDataRoot() });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.measurements() });
|
||||
void refetchInactiveDailyReads(queryClient);
|
||||
setDraft(null);
|
||||
toast.success(t("settings.sections.sources.saveSuccess"));
|
||||
},
|
||||
@@ -179,7 +184,12 @@ export function SourcesSection() {
|
||||
},
|
||||
onSuccess: (saved) => {
|
||||
queryClient.setQueryData(queryKeys.sourcePriority(), saved);
|
||||
// Same fan-out as the save arm: the defaults change the canonical-row
|
||||
// picks everywhere the overrides did.
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.analytics() });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.chartDataRoot() });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.measurements() });
|
||||
void refetchInactiveDailyReads(queryClient);
|
||||
setDraft(null);
|
||||
toast.success(t("settings.sections.sources.resetSuccess"));
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Switch } from "@/components/ui/switch";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { SettingsInfoTile } from "./_info-tile";
|
||||
import { useTranslations, useFormatters } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { queryKeys, refetchInactiveDailyReads } from "@/lib/query-keys";
|
||||
import {
|
||||
METRIC_BOUNDS,
|
||||
type ThresholdMetric,
|
||||
@@ -95,6 +95,9 @@ export function ThresholdsEditorSection({ id }: { id: string }) {
|
||||
// Every chart/band depends on these thresholds — invalidate everything.
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.analytics() });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
|
||||
// The dashboard bands + score context read the same thresholds through
|
||||
// the daily reads, which are unmounted on Settings — force the refetch.
|
||||
void refetchInactiveDailyReads(queryClient);
|
||||
toast.success(t("thresholds.saveSuccess"));
|
||||
},
|
||||
onError: (err) =>
|
||||
@@ -115,6 +118,8 @@ export function ThresholdsEditorSection({ id }: { id: string }) {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.userThresholds() });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.analytics() });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
|
||||
// Same daily-reads truth as the save arm above.
|
||||
void refetchInactiveDailyReads(queryClient);
|
||||
toast.success(t("thresholds.resetSuccess"));
|
||||
},
|
||||
onError: () => toast.error(t("thresholds.saveError")),
|
||||
|
||||
Reference in new issue
Block a user