Project: doctolib-doctolib · Branch: master · Mode: ` · **Model:**claude-haiku-4-5-20251001`
Stats: 1 user messages, 29 tool calls, tools used: Bash, Glob, Grep, Read.
Conversation
Turn 1 — User
I need to understand how documents are fetched and cached after upload:
- Find the queries/hooks that fetch the document list (likely in engines/documents)
- Look for how the document preview/show functionality works
- Find what queries are used when viewing a document (besides decryptedDocuments)
- Look for any prefetch or cache dependencies between: - Document list queries - Individual document queries - The decryptedDocuments query we already saw
The issue is that after upload, the document can't be viewed immediately but works after cache expiry (5 min) or logout.
Turn 1 — Assistant
I'll help you understand the document fetching and caching flow. Let me start by exploring the relevant files in the documents engine and related packages.
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib -type f -name "*.ts" -o -name "*.tsx" -o -name "*.rb" | grep -i document | head -30
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/app/models/security/tanker/group_document_reshare.rb /Users/USER/workspace/doctolib/doctolib/app/models/consultation_document_template.rb /Users/USER/workspace/doctolib/doctolib/app/models/consultation_document_templates_speciality.rb /Users/USER/workspace/doctolib/doctolib/app/models/processors/update/patient_account_documents/document.rb /Users/USER/workspace/doctolib/doctolib/app/models/processors/update/patient_account_documents/documents_cloned_event. …(truncated, 2428 more chars)
Let me search more specifically in the documents engine and patient packages:
Tools used:
Glob:engines/documents/**/*.{ts,tsx,rb}
Tools used:
Glob:engines/patient_accounts/**/*.{ts,tsx,rb}
Tools used:
Glob:packages/@doctolib/**/*document*.{ts,tsx}
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/public/helpers/document.ts /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/public/services/documentEncryption.ts /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/queries/patient_account_documents/appointments/documentAccesses.ts /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/queries/patient_acco …(truncated, 6801 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/engines/documents/app/components/documents/react/clear_document_form.rb /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/concerns/documents/clear_document_render.rb /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/concerns/documents/parse_signed_id.rb /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/admin/accounts_controller.rb /Users/USER/workspace/doctolib/doctolib/engin …(truncated, 11046 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/engines/patient_accounts/app/controllers/patient_accounts/admin/accounts/subscription_synchronizations_controller.rb /Users/USER/workspace/doctolib/doctolib/engines/patient_accounts/app/controllers/patient_accounts/admin/debug_pages/subscriptions_controller.rb /Users/USER/workspace/doctolib/doctolib/engines/patient_accounts/app/controllers/patient_accounts/application_controller.rb /Users/USER/workspace/doctolib/doctolib/engines/patient_accounts/app/contro …(truncated, 11994 more chars)
Now let me search for React Query hooks related to documents and decrypted documents:
Tools used:
Grep:decryptedDocuments|useDecryptedDocuments
Tools used:
Grep:useQuery.*document|queryKey.*document
Tool results:
→ result (ok): No files found
Tool results:
→ result (ok): No files found
Let me search more broadly:
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib/packages -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "decryptedDocuments" | head -10
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/HomeDocuments.tsx /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/doctor-documents/DoctorDocuments.tsx /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/SharedDocuments.tsx /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/mobi …(truncated, 950 more chars)
Perfect! Now let me read the key files:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/hooks/useDecryptedDocuments.ts
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/documents-ui/src/data/queries/documents.ts
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/queries/patient_account_documents/accounts/documents.ts
Tool results:
→ result (ok): 1→import type { Account } from 'monolith_DEPRECATED/types/api/Account' 2→import type { DecryptRecord } from 'engines/e2ee/public' 3→import type PatientAccountDocument from '@doctolib/patient_account_documents/src/types/PatientAccountDocument' 4→import i18n from 'monolith_DEPRECATED/services/i18n' 5→import { useCallback, useContext, useEffect, useState } from 'react' 6→import { TankerContext } from 'engines/e2ee/public' 7→import useAccount from 'monolith_DEPRECATED/h …(truncated, 3614 more chars)
Tool results:
→ result (ok): 1→/ 2→ * As part of the react query migration to v5, we are moving the queries to this file. 3→ * This file is replacing engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts 4→ * Feel free to add endpoints from the old file to this new one, and fix eventual react query issues. 5→ / 6→ 7→import { useContext } from 'react' 8→import { HttpClientContext } from '@doctolib/shared_context' 9→import { useMutation, useQuery, useQuery …(truncated, 12390 more chars)
Tool results:
→ result (ok): 1→import type { HttpClientType } from 'monolith_DEPRECATED/types/http-client' 2→import type PatientAccountDocument from '@doctolib/patient_account_documents/src/types/PatientAccountDocument' 3→import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query-v5' 4→import { useContext, useMemo } from 'react' 5→import { HttpClientContext } from 'monolith_DEPRECATED/contexts/ReactQueryProvider' 6→import useAccount from 'monolith_DEPRECATED/hooks …(truncated, 9816 more chars)
Now let me look at the component files that use these hooks:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/HomeDocuments.tsx(1–100)
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "useDocumentsIndex\|useDocumentsShow" | head -15
Tool results:
→ result (ok): 1→import { useNavigate } from 'react-router-dom' 2→import i18n from 'monolith_DEPRECATED/services/i18n' 3→import { Button } from '@doctolib/design-system/Button' 4→import useRequireTankerVerification from 'monolith_DEPRECATED/hooks/patient/useRequireTankerVerification' 5→import type PatientAccountDocument from '@doctolib/patient_account_documents/src/types/PatientAccountDocument' 6→import DocumentsListSkeleton from '@doctolib/patient_account_documents/src/components/shar …(truncated, 4014 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/patient/patient-file-tabs/consultation/ConsultationDocumentForm/ConsultationDocumentForm.tsx /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/patient/patient-file-tabs/consultation/TreatmentPlan/ButtonsSection/PrintConsultationDocumentsButton.tsx /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/mobile/pages/ShowDocument.tsx / …(truncated, 1781 more chars)
Let me check the document show/view logic and how documents are accessed:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/modules/DocumentItemModule
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/modules/DocumentItemModule -type f
Tool results:
→ result (ERROR): EISDIR: illegal operation on a directory, read
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/modules/DocumentItemModule/index.tsx /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/modules/DocumentItemModule/views/DocumentActions.tsx /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/modules/DocumentItemModule/views/DocumentDesktopItem.jsx /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/s …(truncated, 345 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/modules/DocumentItemModule/index.tsx(1–150)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/modules/DocumentItemModule/views/DocumentActions.tsx(1–100)
Tool results:
→ result (ok): 1→import type PatientAccountDocument from '@doctolib/patient_account_documents/src/types/PatientAccountDocument' 2→import { useReducer } from 'react' 3→import { isOnDesktop, isPartnerDomain } from 'monolith_DEPRECATED/packages/device/environment' 4→import useToast from 'monolith_DEPRECATED/hooks/useToast' 5→import { ALWAYS_SHOW } from 'monolith_DEPRECATED/components/base/Toast' 6→import { 7→ OnDocumentUpdatedOperationKind, 8→ type OnDocumentUpdatedCallback,
…(truncated, 5312 more chars)
Tool results:
→ result (ok): 1→import type { MouseEventHandler, TouchEventHandler } from 'react' 2→import type PatientAccountDocument from '@doctolib/patient_account_documents/src/types/PatientAccountDocument' 3→import i18n from 'monolith_DEPRECATED/services/i18n' 4→import { isOnDesktop } from 'monolith_DEPRECATED/packages/device/environment' 5→import { Dropdown } from '@doctolib/design-system/Dropdown' 6→import { Icon } from '@doctolib/design-system/Icon' 7→import { IconButton } from '@doctoli …(truncated, 3517 more chars)
Now let me look at the useFetchAndDecryptDocument hook:
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib -type f -name "useFetchAndDecryptDocument*"
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "useFetchAndDecryptDocument" | head -5
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/public/hooks/useFetchAndDecryptDocument.ts /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/hooks/useFetchAndDecryptDocument.ts
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/engines/check_in/app/assets/javascripts/modules/DocumentRequestFunnelContentModule/views/DocumentRequestItemDraft.tsx /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/public/hooks/useFetchAndDecryptDocument.ts /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/hooks/useFetchAndDecryptDocument.ts /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/ …(truncated, 202 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/hooks/useFetchAndDecryptDocument.ts
Tools used:
Bash:grep -r "queryKey.*document\|buildDocumentsShowQueryKey\|queryKeyShow" /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src --include="*.ts" --include="*.tsx" …(truncated, 10 more chars)
Tool results:
→ result (ok): 1→import { useContext } from 'react' 2→import i18n from 'monolith_DEPRECATED/services/i18n' 3→import { useQuery } from '@tanstack/react-query-v5' 4→import { HttpClientContext } from 'monolith_DEPRECATED/contexts/ReactQueryProvider' 5→import type { HttpClientType } from 'monolith_DEPRECATED/types/http-client' 6→import { TankerContext } from 'engines/e2ee/public' 7→import { handleEncryptedDocument } from 'monolith_DEPRECATED/components/shared/documents/utils' 8→i …(truncated, 3549 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/queries/patient_account_documents/accounts/unreadDocuments.ts:const queryKey = ['account', 'unread_documents'] /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/queries/patient_account_documents/accounts/documents.ts:const queryKeyShow = (documentId?: string) => ['account', 'documents', documentId] /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_accou …(truncated, 984 more chars)
Now let me check the document upload/creation flow:
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents -type f -name "*[Uu]pload*" | head -10
Tools used:
Bash:grep -r "POST.*documents\|mutationFn.*documents.*post\|invalidateQueries.*document" /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src --include="*.ts" --inc …(truncated, 23 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/public/components/DocumentUpload.ts /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/public/components/NativeDocumentUploader.ts /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentUpload.tsx /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/sha …(truncated, 648 more chars)
Tool results:
→ result (ok)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentUploadModule.tsx(1–150)
Tool results:
→ result (ok): 1→import ErrorBoundary from 'monolith_DEPRECATED/components/shared/ErrorBoundary' 2→import type { DocumentUploadProps } from '@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentUpload' 3→import DocumentUpload from '@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentUpload' 4→ 5→const DocumentUploadModule = (props: DocumentUploadProps) => ( 6→ <ErrorBoundary component="DocumentUpload" team="phr">
…(truncated, 470 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentUpload.tsx(1–200)
Tool results:
→ result (ok): 1→import type { ChangeEvent, MutableRefObject, ReactNode, SyntheticEvent } from 'react' 2→import { useCallback, useContext, useEffect, useRef, useState } from 'react' 3→import { v4 as uuidv4 } from 'uuid' 4→import { compact, partition } from 'lodash-es' 5→import constants from 'monolith_DEPRECATED/services/constants' 6→import i18n from 'monolith_DEPRECATED/services/i18n' 7→import { versionFulfillsRequirement } from 'monolith_DEPRECATED/services/react-native/appVersi …(truncated, 10152 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentUpload.tsx(200–350)
Tool results:
→ result (ok): 200→ document_upload_funnel_id: productEventDetails.document_upload_funnel_id, 201→ context: productEventDetails.context as DocumentContext, 202→ document_flow: productEventDetails.document_flow as DocumentFlow, 203→ document_error_type: DocumentUploadErrorType.FilePermissionError, 204→ }) 205→ 206→ return false 207→ } 208→ 209→ throw error 210→ } 211→ 212→ return true 213 …(truncated, 6141 more chars)
Tools used:
Bash:grep -r "DocumentSaver\|fetchDocuments\|onUpload" /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule --include="*.ts" …(truncated, 28 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentSaver.test.tsx:import DocumentSaver from './DocumentSaver' /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentSaver.test.tsx:import { computePersonalizedServicesCheckboxContext } from './DocumentSaverCheckbox' /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_ac …(truncated, 3261 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentSaver.tsx(1–200)
Tool results:
→ result (ok): 1→import type { Dispatch, ReactNode } from 'react' 2→import { useContext, useState } from 'react' 3→import i18n from 'monolith_DEPRECATED/services/i18n' 4→import { Modal } from '@doctolib/design-system/Modal' 5→import { SpinnerLoader } from '@doctolib/design-system/SpinnerLoader' 6→import QuitDialog from 'monolith_DEPRECATED/components/shared/documents/QuitDialog' 7→import { TankerContext } from 'engines/e2ee/public' 8→import { isOnMobile } from 'monolith_DEPRE …(truncated, 6170 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentSaverForm.tsx(1–200)
Tool results:
→ result (ok): 1→import { Button } from '@doctolib/design-system/Button' 2→import { Icon } from '@doctolib/design-system/Icon' 3→import { InputText } from '@doctolib/design-system/InputText' 4→import type { FieldState } from '@doctolib/design-system/Field' 5→import { Select } from '@doctolib/design-system/Select' 6→import { decode } from '@doctolib/security' 7→import Form from 'monolith_DEPRECATED/components/base/form/Form' 8→import type { DocumentScannerModule } from 'monoli …(truncated, 10673 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentSaverForm.tsx(200–350)
Tool results:
→ result (ok): 200→ message: i18n.t('common.file_too_big', { size: toHumanReadableFileSize(constants.DOCUMENT_MAX_SIZE) }), 201→ }) 202→ return 203→ } 204→ 205→ onAdd?.(fileToUpload) 206→ 207→ try { 208→ const [encryptedFileToUpload, tankerEncryptedTitle] = await Promise.all([ 209→ encryptDocument(fileToUpload, decode(master_patient_id) as number), 210→ title ? encryptDocumentTitle(title, decode(master_patient_id) as number) : null,
…(truncated, 7747 more chars)
Perfect! Now I have enough information. Let me create a comprehensive analysis document:
Summary
I've thoroughly explored the document fetching and caching flow in the Doctolib codebase. Here's what I found:
Key Query Hooks & Cache Keys
1. Patient Account Documents (Patient's Drive)
Located in /packages/@doctolib/patient_account_documents/src/queries/patient_account_documents/accounts/documents.ts:
useDocumentsIndex()- Fetches paginated list of documents- Query Key:
['account', 'documents', { master_patient_id }] - Endpoint:
GET /account/documents.json - Uses infinite query with pagination
-
Cache time: Not explicitly set (uses defaults)
-
useDocumentsShow()- Fetches individual document metadata - Query Key:
['account', 'documents', documentId] - Endpoint:
GET /account/documents/{id} - Decrypts title using
decryptTitle() -
Cache time: Not explicitly set
-
useFetchAndDecryptDocument()- Fetches and decrypts document file - Query Key:
['account', 'documents', documentId, 'decrypted'] - Endpoint: Uses presigned URL to download encrypted content
- Stale Time: 30 minutes
- GC Time: 30 minutes
- Calls
handleEncryptedDocument()to decrypt the file
2. Doctor App Documents
Located in /packages/@doctolib/documents-ui/src/data/queries/documents.ts:
useDocumentsIndex()- Fetches doctor app documents- Query Key:
['documents', 'doctor_app', 'documents', 'index', { patient_id, consultation_id, appointment_id }] - Endpoint:
GET /doctor_app/documents.json - Decrypts documents after fetching
-
Also has mutation hook for updates
-
useDocumentsUpdate()- Mutation for updating documents - Invalidates:
buildDocumentsShowQueryKey(props.documentId) - Invalidates:
buildDocumentsIndexQueryKey({ patient_id })
3. Title Decryption Hook
Located in /packages/@doctolib/patient_account_documents/src/hooks/useDecryptedDocuments.ts:
useDecryptedDocuments()- Decrypts encrypted titles- Calls
decryptTitle()for each document - Returns
decryptedDocumentsstate - Triggers when
tankerIsReadychanges - This is client-side decryption using TankerContext
The Upload Flow & Cache Problem
Located in /packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/:
Upload Process: 1. DocumentUpload.tsx - Main upload component 2. DocumentSaver.tsx - Form wrapper 3. DocumentSaverForm.tsx - The actual form with submission logic
Key line from DocumentSaverForm.tsx (line 228-237):
const { responseBody, status } = await DEPRECATED_post('/account/documents', body)
if (status >= 400) {
onFailure({ errorType: DocumentUploadErrorType.BackendError })
} else {
const { document } = JSON.parse(responseBody)
const documentWasSaved = true
onHide({ documentWasSaved })
await fetchDocuments?.() // <-- Manual refetch
onUpload(document)
}
The Problem:
- After
POST /account/documentssucceeds, the backend returns the createddocumentobject - The code calls
fetchDocuments?.()(optional callback) andonUpload(document) - BUT - there's NO React Query cache invalidation or update here!
- The component manually calls a callback to refetch if provided, but:
- This refetch is not guaranteed to be
useDocumentsIndex()- The individual document query['account', 'documents', documentId]is never invalidated - The decrypted file query['account', 'documents', documentId, 'decrypted']stays in cache
Why it Works After 5 Minutes or Logout
- Cache Expiry: The decrypted file query has a 30-minute stale time, but React Query may consider it stale and refetch
- Logout: Clears all React Query cache automatically
- Manual Refetch: Logout and re-login forces new queries
Prefetch Dependencies
The code has NO explicit prefetch dependencies:
- There's no code that prefetches useDocumentsShow() when useDocumentsIndex() loads
- There's no code that prefetches useFetchAndDecryptDocument() based on document list
- Decryption happens lazily when user interacts with a document
Root Cause Summary
File: /packages/@doctolib/patient_account_documents/src/components/shared/DocumentUploadModule/DocumentSaverForm.tsx (lines 228-237)
Issue: After document upload:
- The newly created document is NOT added to React Query cache
- The useDocumentsIndex() query is NOT invalidated
- If fetchDocuments callback is not provided or doesn't call the right query invalidation, cache remains stale
- User sees old list or can't view new document immediately
Required Fix:
- After successful upload, need to invalidate or update the following query keys:
- ['account', 'documents', { master_patient_id }] (list cache)
- Optionally add the new document to cache directly to avoid refetch
- Ensure proper cache invalidation using React Query's queryClient.invalidateQueries()