fix(query-keys): refresh the day view and timeline after every write

Symptoms, workouts, lab results, allergies, documents and questionnaires
evicted only their own lists. An open day, its calendar dots and the
timeline kept the pre-write answer for up to five minutes, so "Capture
for this day" saved a symptom or a workout and the day showed the old
list, and a deleted row stayed on it.

Workouts, labs, allergies and documents get a bundle beside the seven
that already carry the day and timeline roots; the six lab lists become
one (the OCR dialog's own copy of the commit hook's list goes with it).
Symptoms and questionnaires add the two roots to their list. Symptoms
stay off the illness bundle on purpose: nothing on the dashboard or the
Today digest reads them. Custom metrics need nothing: neither the day
nor the timeline reads them. invalidateKeys now drops a key listed twice,
since spread bundles share the two roots.
This commit is contained in:
Marc-André Bombeck committed 2026-11-11 19:41:14 +01:00
1 parent aae029d94e
commit 2e73b0ffec
24 files changed
+257 -118

No files matched your search

@@ -18,7 +18,11 @@ import {
import { DOCUMENT_PURGE_GRACE_DAYS } from "@/lib/documents/inbound-constants";
import { showErrorToast } from "@/lib/errors/error-toast";
import { useTranslations } from "@/lib/i18n/context";
import { invalidateKeys, queryKeys } from "@/lib/query-keys";
import {
documentDependentKeys,
invalidateKeys,
queryKeys,
} from "@/lib/query-keys";
import type {
DocumentSourceSystemValue,
InboundDocumentDetailDto,
@@ -265,7 +269,7 @@ function useDocumentWrites({
// A link written from this side changes what the visit's and the
// dose's own views list, so their reads refresh with the vault's.
void invalidateKeys(queryClient, [
queryKeys.documents(),
...documentDependentKeys,
...(input.encounterIds !== undefined ? [queryKeys.encounters()] : []),
...(input.vaccinationIds !== undefined
? [queryKeys.vaccinations()]
@@ -282,7 +286,7 @@ function useDocumentWrites({
mutationFn: (id: string) =>
apiPost(`/api/documents/inbound/${id}/restore`, {}),
onSuccess: () => {
void invalidateKeys(queryClient, [queryKeys.documents()]);
void invalidateKeys(queryClient, documentDependentKeys);
},
onError: (error) => {
const reason =
@@ -303,7 +307,7 @@ function useDocumentWrites({
mutationFn: (id: string) => apiDelete(`/api/documents/inbound/${id}`),
onSuccess: (_data, id) => {
onOpenChange(false);
void invalidateKeys(queryClient, [queryKeys.documents()]);
void invalidateKeys(queryClient, documentDependentKeys);
toast.success(t("documents.toast.deleted"), {
description: t("documents.toast.deletedHint", {
days: DOCUMENT_PURGE_GRACE_DAYS,
@@ -71,7 +71,11 @@ import {
type DocumentSourceTagDto,
} from "@/lib/documents/sources/types";
import { useTranslations } from "@/lib/i18n/context";
import { invalidateKeys, queryKeys } from "@/lib/query-keys";
import {
documentDependentKeys,
invalidateKeys,
queryKeys,
} from "@/lib/query-keys";
import { cn } from "@/lib/utils";
import type { InboundDocumentKindValue } from "@/lib/validations/inbound-documents";
@@ -317,7 +321,7 @@ export function DocumentSourcePicker({
// The vault, its pickers and the record's own lists read documents
// through the `["documents"]` prefix; the picker's states through its own.
void invalidateKeys(queryClient, [
queryKeys.documents(),
...documentDependentKeys,
queryKeys.documentSources(),
]);
if (handed.length > 0) onImported?.(handed);
@@ -10,7 +10,11 @@ import { ApiError, apiGetList, apiPost } from "@/lib/api/api-fetch";
import { DOCUMENT_BULK_MAX_IDS } from "@/lib/documents/inbound-constants";
import { showErrorToast } from "@/lib/errors/error-toast";
import { useTranslations } from "@/lib/i18n/context";
import { invalidateKeys, queryKeys } from "@/lib/query-keys";
import {
documentDependentKeys,
invalidateKeys,
queryKeys,
} from "@/lib/query-keys";
import type {
DeletedDocumentListDto,
DocumentBulkResultDto,
@@ -21,7 +25,8 @@ import type {
* document back (one at a time, or all through the bulk route); "Delete now"
* removes one for good ahead of the grace window. Every write invalidates the
* `["documents"]` prefix, so the vault, its usage figures and this list repaint
* together.
* together, and the day and timeline roots, which place a document on its
* date.
*/
export function useRecentlyDeleted({ enabled }: { enabled: boolean }) {
const { t } = useTranslations();
@@ -36,7 +41,7 @@ export function useRecentlyDeleted({ enabled }: { enabled: boolean }) {
});
const refresh = useCallback(
() => void invalidateKeys(queryClient, [queryKeys.documents()]),
() => void invalidateKeys(queryClient, documentDependentKeys),
[queryClient],
);
@@ -32,7 +32,7 @@ import { toast } from "sonner";
import { useTranslations } from "@/lib/i18n/context";
import { DOCUMENT_UPLOAD_PER_USER_CONCURRENCY } from "@/lib/documents/inbound-constants";
import { randomId } from "@/lib/random-id";
import { invalidateKeys, queryKeys } from "@/lib/query-keys";
import { documentDependentKeys, invalidateKeys } from "@/lib/query-keys";
import { uploadWithBusyRetry } from "./upload-busy-retry";
import {
parseUploadResponse,
@@ -261,7 +261,7 @@ export function useDocumentUpload(
}
const decision = settleDecision(result);
removeItem(localId);
void invalidateKeys(queryClient, [queryKeys.documents()]);
void invalidateKeys(queryClient, documentDependentKeys);
if (decision.toastDuplicate) {
toast.info(t("documents.toast.duplicate"));
}
@@ -11,7 +11,7 @@ import {
} from "@/lib/documents/inbound-constants";
import { showErrorToast } from "@/lib/errors/error-toast";
import { useTranslations } from "@/lib/i18n/context";
import { invalidateKeys, queryKeys } from "@/lib/query-keys";
import { documentDependentKeys, invalidateKeys } from "@/lib/query-keys";
import type {
DocumentBulkAction,
DocumentBulkResultDto,
@@ -64,7 +64,7 @@ export function useVaultBulk({
return results;
},
onSettled: () => {
void invalidateKeys(queryClient, [queryKeys.documents()]);
void invalidateKeys(queryClient, documentDependentKeys);
},
});