mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix(dashboard): surface freshly-added and synced measurements without a reload
A measurement added in-app left the dashboard hero, score ring and tile strip showing yesterday's value until a hard reload. The snapshot query carries refetch-on-mount/focus off and a 120 s poll, but the measurement mutation bundle never invalidated the snapshot key, so an in-app create / update / delete updated every other surface and left the Startseite stale. Add the snapshot key to the measurement-dependent bundle, mirroring the medication fix; the manual routes already hard-evict the server snapshot bucket, so the triggered refetch returns post-write data at once. Background syncs (Withings, Apple Health, iOS batch) fire no client mutation event, so the keys were never invalidated and the dashboard only caught up on the 120 s poll or a reload. Refetch the snapshot on window focus so returning to the tab after a sync surfaces the new readings; the 60 s staleTime keeps rapid focus toggles free and the warm server cache keeps the focus refetch cheap.
This commit is contained in:
1 parent
a5b3715a46
commit
2d5c4cfe46
4 files changed
+49
-9
No files matched your search
@@ -219,6 +219,16 @@ describe("dependent-key bundles", () => {
|
||||
expect(keyStrings).toContain(JSON.stringify(["chart-data"]));
|
||||
});
|
||||
|
||||
// v1.18.9 (#38) — the dashboard snapshot must ride the measurement
|
||||
// bundle too. The hero band / score ring / tile strip read ONE snapshot
|
||||
// query with refetch-on-mount/focus options that otherwise leave an
|
||||
// in-app-added reading invisible on the Startseite until the 120 s poll
|
||||
// or a hard reload. Mirror the v1.16.11 medication-bundle fix.
|
||||
it("measurementDependentKeys bundles the dashboard snapshot (v1.18.9)", () => {
|
||||
const keyStrings = measurementDependentKeys.map((k) => JSON.stringify(k));
|
||||
expect(keyStrings).toContain(JSON.stringify(["dashboard", "snapshot"]));
|
||||
});
|
||||
|
||||
it("moodDependentKeys bundle covers mood + analytics + targets", () => {
|
||||
const keyStrings = moodDependentKeys.map((k) => JSON.stringify(k));
|
||||
expect(keyStrings).toContain(JSON.stringify(["mood-entries"]));
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
* backgrounded (`refetchIntervalInBackground: false`). The poll rides
|
||||
* the warm 60 s server cache and never touches the LLM surfaces.
|
||||
*
|
||||
* The hook keeps its existing warm-cache options (`staleTime: 60_000`,
|
||||
* `refetchOnMount: false`, `refetchOnWindowFocus: false`) so a
|
||||
* return-to-dashboard within a minute stays a free cache hit, and the
|
||||
* queryKey stays the centralised factory entry.
|
||||
* The hook keeps its warm-cache options (`staleTime: 60_000`,
|
||||
* `refetchOnMount: false`) so a return-to-dashboard within a minute
|
||||
* stays a free cache hit, and the queryKey stays the centralised factory
|
||||
* entry. v1.18.9 (#38) flips `refetchOnWindowFocus` to `true` so a
|
||||
* background sync — which fires no client mutation event — surfaces on
|
||||
* return-to-tab; the `staleTime` gate keeps rapid focus toggles free.
|
||||
*/
|
||||
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||
|
||||
@@ -70,7 +72,12 @@ describe("useDashboardSnapshot — auto-refresh on an open page", () => {
|
||||
const opts = lastOpts();
|
||||
expect(opts.staleTime).toBe(60_000);
|
||||
expect(opts.refetchOnMount).toBe(false);
|
||||
expect(opts.refetchOnWindowFocus).toBe(false);
|
||||
});
|
||||
|
||||
it("refetches on window focus so a background sync surfaces on return", () => {
|
||||
useDashboardSnapshot();
|
||||
const opts = lastOpts();
|
||||
expect(opts.refetchOnWindowFocus).toBe(true);
|
||||
});
|
||||
|
||||
it("routes the queryKey through the centralised factory", () => {
|
||||
|
||||
@@ -14,9 +14,17 @@
|
||||
* already passed the auth / onboarding redirect. Firing un-gated
|
||||
* removes the `/api/auth/me` round-trip from the cold critical path
|
||||
* (R-firstpaint §1a — the single biggest first-byte win).
|
||||
* - `staleTime: 60_000` + `refetchOnMount: false` +
|
||||
* `refetchOnWindowFocus: false` mirror `DASHBOARD_QUERY_OPTS` so a
|
||||
* return-to-dashboard within a minute is a free cache hit.
|
||||
* - `staleTime: 60_000` + `refetchOnMount: false` keep a
|
||||
* return-to-dashboard within a minute a free cache hit.
|
||||
* - `refetchOnWindowFocus: true` (v1.18.9) closes the background-sync
|
||||
* freshness gap behind #38: a Withings / Apple-Health / iOS batch
|
||||
* sync writes the user's data with NO client-side mutation event, so
|
||||
* the snapshot keys are never invalidated and the dashboard only
|
||||
* updated on the 120 s poll or a hard reload. Refetch-on-focus means
|
||||
* returning to the tab after a sync surfaces the new readings within
|
||||
* a frame. The `staleTime: 60_000` gate makes rapid focus toggles
|
||||
* cheap (a refetch fires only once the slot is stale), and the warm
|
||||
* 60 s server SWR cache keeps the focus refetch sub-cost.
|
||||
* - `refetchInterval: 120_000` + `refetchIntervalInBackground: false`
|
||||
* keep an open dashboard live: an idle tab polls the snapshot every
|
||||
* two minutes so freshly-synced Withings / HealthKit readings appear
|
||||
@@ -162,7 +170,11 @@ export function useDashboardSnapshot(enabled = true) {
|
||||
enabled,
|
||||
staleTime: 60_000,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
// v1.18.9 (#38) — refetch on focus so a background sync (Withings /
|
||||
// Apple Health / iOS batch), which produces no client mutation event,
|
||||
// surfaces on return-to-tab. Gated by the 60 s `staleTime` above so a
|
||||
// rapid focus toggle within the window is still a free cache hit.
|
||||
refetchOnWindowFocus: true,
|
||||
refetchInterval: DASHBOARD_REFETCH_INTERVAL_MS,
|
||||
refetchIntervalInBackground: false,
|
||||
// v1.16.8 — one retry on network errors / 5xx (never 401/403). A
|
||||
|
||||
@@ -58,6 +58,16 @@ export const queryKeys = {
|
||||
* TanStack's hierarchical-prefix semantics — adding a measurement now
|
||||
* refreshes the tile strip *and* the chart row in lockstep instead of
|
||||
* leaving the chart row 60 s stale (audit C2).
|
||||
*
|
||||
* v1.18.9 — `dashboardSnapshot` joins the bundle, mirroring the
|
||||
* v1.16.11 medication fix. The dashboard hero band, score ring, and
|
||||
* tile strip all read ONE snapshot query configured with
|
||||
* refetchOnMount/WindowFocus off and a 120 s poll; without the key here
|
||||
* a blood-pressure (or any) reading added in-app stayed invisible on the
|
||||
* Startseite until the poll ticked or a hard reload — the reported #38
|
||||
* stale-read. The manual create / update / delete routes already
|
||||
* hard-evict the server snapshot bucket (`{ evict: true }`), so the
|
||||
* refetch this invalidation triggers returns post-write data at once.
|
||||
*/
|
||||
export const measurementDependentKeys = [
|
||||
queryKeys.measurements(),
|
||||
@@ -65,6 +75,7 @@ export const measurementDependentKeys = [
|
||||
queryKeys.insightsRoot(),
|
||||
queryKeys.insightsTargets(),
|
||||
queryKeys.gamificationAchievements(),
|
||||
queryKeys.dashboardSnapshot(),
|
||||
["chart-data"] as const,
|
||||
// v1.8.5 — re-run the diversity-nudge clustering when readings change.
|
||||
["measurement-diversity"] as const,
|
||||
|
||||
Reference in new issue
Block a user