Project: doctolib-doctolib · Branch: master · Mode: · Model: claude-haiku-4-5-20251001 Stats: 1 user messages, 29 tool calls, tools used: Bash, Grep, Glob, Read. Conversation Turn 1 — User I need to understand a document import issue where: 1. When UPLOAD_DOCUMENT_WRITE feature switch is on, documents are imported via /v2 endpoints (PUT/POST) 2. After successful import, viewing the document immediately fails 3. After waiting ~5 minutes or logout/login, viewing works Please explore: 1. Find where UPLOAD_DOCUMENT_WRITE feature switch is used in the document upload flow 2. Look for /v2 endpoints for document upload (PUT/POST operations) 3. Find how documents are uploaded/created when this FS is enabled 4. Look for React Query cache invalidation after document uploads (specifically related to the decryptedDocuments query) 5. Check if there's a queryClient.invalidateQueries or similar after document creation Focus on the documents engine and look for upload/import components and mutations. Turn 1 — Assistant I'll help you explore this document import issue. Let me search for the relevant code related to the UPLOAD_DOCUMENT_WRITE feature switch, document upload endpoints, and cache invalidation. Tools used: - Bash: find /Users/USER/workspace/doctolib/doctolib -type f -name ".ts" -o -name ".tsx" -o -name ".rb" | head -20 Tool results: → result (ok): /Users/USER/workspace/doctolib/doctolib/sorbet/tapioca/require.rb /Users/USER/workspace/doctolib/doctolib/sorbet/tapioca/compilers/module_constant_shortcuts.rb /Users/USER/workspace/doctolib/doctolib/docker/config/pitchfork.conf.rb /Users/USER/workspace/doctolib/doctolib/docker/scripts/generate_app_version.rb /Users/USER/workspace/doctolib/doctolib/app/middleware/error_event_id_injector.rb /Users/USER/workspace/doctolib/doctolib/app/middleware/unique_ssid_counter.rb /Users/USER/workspace/doctoli …(truncated, 1167 more chars) Let me search for the UPLOAD_DOCUMENT_WRITE feature switch usage and document upload endpoints. Tools used: - Grep: UPLOAD_DOCUMENT_WRITE in /Users/USER/workspace/doctolib/doctolib Tool results: → result (ok): No files found Let me try a broader search for document upload related files: Tools used: - Glob: /documents//.ts in /Users/USER/workspace/doctolib/doctolib Tools used: - Grep: decryptedDocuments in /Users/USER/workspace/doctolib/doctolib Tools used: - Grep: invalidateQueries in /Users/USER/workspace/doctolib/doctolib Tool results: → result (ok): Output too large (22.4KB). Full output saved to: /Users/USER/.claude/projects/-Users-clertonra-workspace-doctolib-doctolib/ec3195ed-8479-410c-9fb2-c5774097f38e/tool-results/toolu_0131b7smdgqj8bBAF87mR8vz.txt Preview (first 2KB): Found 292 files packages/@doctolib/siilo_ui/src/queries/siiloPatientCases/siiloPatientCases.ts app/assets/javascripts/queries/configuration/documentCustomizations.ts engines/siilo_integration/app/assets/javascripts/queries/siilo_integration/tele_exper …(truncated, 1746 more chars) Tool results: → result (ok): /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/patient/patient-file-tabs/documents/Documents.tsx /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/useNavigateAfterImport/useEhrPtNavigateAfterImport.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/useNavigateAfterImport/useDefaultNavigateAfterImport.ts /Users/USER/workspace/doctolib/doct …(truncated, 13459 more chars) Tool results: → result (ok): Found 44 files packages/@doctolib/patient_messaging_pro_sdk/src/modules/doctor_app/CreateMessageButtonModule/useCreateMessageButtonModuleController.ts packages/@doctolib/patient_messaging_pro_sdk/src/modules/doctor_app/CreateMessageButtonModule/useCreateMessageButtonModuleController.test.tsx packages/@doctolib/financial_os_fr_billing/package.json packages/@doctolib/lab-integration/src/data/lab-report.ts app/assets/javascripts/components/doctor/desktop/services/export-medical-folder/pdfMedicalFol …(truncated, 3847 more chars) Great! Let me now look at the key files related to document uploads and the decryptedDocuments query: Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/decryptedDocuments.ts Tools used: - Glob: /documents//mutations//.ts in /Users/USER/workspace/doctolib/doctolib Tools used: - Grep: v2.upload|PUT.document|POST.document in /Users/USER/workspace/doctolib/doctolib/engines/documents Tool results: → result (ok): 1→import type { AccountContextType } from '@doctolib/shared_context' 2→import { AccountContext } from '@doctolib/shared_context' 3→import type { DecryptFile } from 'engines/e2ee/public' 4→import { TankerContext } from 'engines/e2ee/public' 5→import { useQuery, useQueryClient } from '@tanstack/react-query' 6→import { handleEncryptedDocument } from 'components/shared/documents/utils' 7→import { HttpClientContext } from 'contexts/ReactQueryProvider' 8→import { use …(truncated, 5373 more chars) Tool results: → result (ok): No files found Tool results: → result (ok): No files found Let me search for upload and document import related files more broadly: Tools used: - Grep: upload|import in /Users/USER/workspace/doctolib/doctolib/engines/documents Tools used: - Glob: /documents//app/controllers//.rb in /Users/USER/workspace/doctolib/doctolib Tool results: → result (ok): Found 30 files limit: 30, offset: 0 engines/documents/app/assets/javascripts/modules/UnifiedDocumentEditModule/types.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/types.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/useDocumentEditModuleController.tsx engines/documents/app/assets/javascripts/hooks/doctor/shared/useDocumentEdit/variants/updateDocumentCategory/updateFacets.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/Docume …(truncated, 2438 more chars) Tool results: → result (ok): /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/engines/documents/app/controllers/documents/admin/documents_controller.rb /Users/USER/workspace/doctolib/doctolib/eng …(truncated, 3751 more chars) Perfect! I found the v2 controllers. Let me examine them: Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/doctor_app/v2/documents_controller.rb Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/doctor_app/v2/presigned_urls_controller.rb Tools used: - Grep: UPLOAD_DOCUMENT_WRITE|UPLOAD_DOCUMENT in /Users/USER/workspace/doctolib/doctolib Tool results: → result (ok): 1→# frozen_string_literal: true 2→ 3→# V2 Documents REST Controller - Drop-in replacement for V1 4→# 5→# This controller provides the same REST API surface as V1 DocumentsController 6→# but uses V3 backend (Documents::V3::DocumentService). 7→# 8→# Architecture: 9→# - Thin wrapper around Documents::V3::DocumentService 10→# - Inherits from V1 controller to reuse all helper methods 11→# - Converts ActionDispatch::UploadedFile to hash for service 1 …(truncated, 13320 more chars) Tool results: → result (ok): 1→# frozen_string_literal: true 2→ 3→module Documents 4→ module DoctorApp 5→ module V2 6→ class PresignedUrlsController < ::Documents::DoctorApp::ApplicationController 7→ authenticate_with Authentication::Api::Filter::ACCOUNT 8→ after_action :verify_authorized 9→ 10→ # POST /documents/doctor_app/v2/documents/:document_id/presigned_url 11→ # Generate presigned URL for a single document 12→ # 13→ …(truncated, 2782 more chars) Tool results: → result (ok): No files found Great! Now let me search for where the feature switch is actually used in the frontend: Tools used: - Grep: feature.switch|featureSwitch|feature_switch in /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts Tools used: - Glob: /documents//Import in /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts Tool results: → result (ok): /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/useNavigateAfterImport/useEhrPtNavigateAfterImport.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/useNavigateAfterImport/useDefaultNavigateAfterImport.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/useNavigateAfterImport/useUseNavigateAfterImportVariant.ts /Users/USER/work …(truncated, 226 more chars) Tool results: → result (ok): Found 11 files limit: 20, offset: 0 engines/documents/app/assets/javascripts/modules/UnifiedDocumentEditModule/types.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/types.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/useDocumentEditModuleController.tsx engines/documents/app/assets/javascripts/hooks/doctor/shared/useDocumentEdit/variants/updateDocumentCategory/updateFacets.ts engines/documents/app/assets/javascripts/modules/DocumentEditModule/Docume …(truncated, 615 more chars) Let me look at the Imports component which likely handles document imports: Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/Imports.tsx Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/ImportsMainPanel.tsx Tool results: → result (ok): 1→import UnifiedDocumentEditModule from 'engines/documents/modules/UnifiedDocumentEditModule/UnifiedDocumentEditModule' 2→import type { UnifiedDocumentEditModuleProps } from 'engines/documents/modules/UnifiedDocumentEditModule/types' 3→import { type ReactNode, type TouchEvent, useContext } from 'react' 4→import { SelectedPatientContext, type DocumentImportsDocumentWithPreview } from '@doctolib/shared_context' 5→import { getDocumentImportOrigin } from 'engines/documents/helper …(truncated, 5154 more chars) Tool results: → result (ok): 1→import DroppableZoneButtonModule from 'modules/shared/desktop/DroppableZoneButtonModule/DroppableZoneButtonModule' 2→import { type ChangeEvent, type TouchEvent, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react' 3→import { useLocation, useNavigate } from 'react-router-dom' 4→import { omit } from 'lodash-es' 5→import { v4 as uuidv4 } from 'uuid' 6→import type { PDFImage } from 'pdf-lib' 7→import { degrees, PDFDocument } from 'pdf-lib' …(truncated, 43460 more chars) Great! Now let me search for the feature switch override constant and the DocumentEditModule which handles uploads: Tools used: - Grep: UPLOAD_DOCUMENT_FEATURE_SWITCH_OVERRIDE in /Users/USER/workspace/doctolib/doctolib Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/modules/DocumentEditModule/useDocumentEditModuleController.tsx (1–200) Tool results: → result (ok): 1→import type { ShareWith } from '@doctolib/deepwater_core/public' 2→import { Button } from '@doctolib/design-system/Button' 3→import { useObservationsCreateFromMail } from '@doctolib/ehr_observations_sdk/public/queries/observations' 4→import { useTrackingsCreate } from '@doctolib/ehr_observations_sdk/public/queries/trackings' 5→import { MedicalObservationCategory } from '@doctolib/ehr_observations_sdk/public/types/MedicalObservationCategory' 6→import { mapDeprecatedObse …(truncated, 9916 more chars) Tool results: → result (ok): Found 4 files packages/@doctolib/documents-ui/src/index.ts packages/@doctolib/documents-ui/src/constants.ts app/assets/javascripts/components/doctor/desktop/pages/documents/ImportsMainPanel.tsx app/assets/javascripts/contexts/doctor/desktop/DocumentImportContext.tsx Now let me look at the documents-ui constants to find the feature switch override: Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/documents-ui/src/constants.ts Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts (1–300) Tool results: → result (ok): 1→import type { FeatureSwitchOverride } from './helpers/types' 2→ 3→// Feature switch override for upload_document_write double-write mode 4→export const UPLOAD_DOCUMENT_FEATURE_SWITCH_OVERRIDE: FeatureSwitchOverride = { 5→ featureSwitch: 'upload_document_write', 6→ mode: 'double-write', 7→} 8→ 9→// Feature switch override for mobile confirmation module upload double-write mode 10→export const MOBILE_CONFIRMATION_UPLOAD_FEATURE_SWITCH_OVERRIDE: Featu …(truncated, 476 more chars) Tool results: → result (ok): 1→/ 2→ @file 3→ @deprecated This file is deprecated and will be removed in future releases. 4→ Please use the new implementation in packages/@doctolib/documents-ui/src/data/queries/documents.ts. 5→ This refactor happens because of the react query migration to v5. 6→ Feel free to move endpoints to this new file. 7→ / 8→ 9→import path from 'path' 10→import { useContext } from 'react' 11→import type { DoctorAppDocument } from '@doctolib/f …(truncated, 12505 more chars) Let me search for where the document upload/create is called and look for cache invalidation: Tools used: - Bash: grep -r "useDocumentsCreate\|createDocument" /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts --include=".ts" --include=".tsx" -l | head -10 Tool results: → result (ok): /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/modules/DocumentEditModule/useDocumentEditModuleController.tsx Let me read more of the documents.ts file to find the useDocumentsCreate mutation: Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts (300–700) Tool results: → result (ok): 300→ 301→ return useQuery({ 302→ queryKey: buildDocumentsShowQueryKey(documentId), 303→ queryFn: () => { 304→ if (!documentId) throw new Error('Unable to fetch a document without documentId') 305→ return getDocument({ documentId, decryptRecord, httpClient }) 306→ }, 307→ enabled: Boolean(enabled && documentId), 308→ onSuccess, 309→ cacheTime, 310→ staleTime, 311→ }) 312→} 313→ 314→export type UseDocumentUpdateMutationResu …(truncated, 10260 more chars) Perfect! Now let me search for where useDocumentsCreate is called with the featureSwitchOverride and look for any cache invalidation after document creation: Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/modules/DocumentEditModule/useDocumentEditModuleController.tsx (200–600) Tools used: - Grep: buildDocumentEndpoint in /Users/USER/workspace/doctolib/doctolib Tool results: → result (ok): 200→ documentURL: null, 201→ }) 202→ 203→ const { 204→ practitionerSignature: practitionerSignatureFromConsultation, 205→ hasSignature: practitionerHasSignature, 206→ humanFullName: practitionerHumanFullName, 207→ } = usePractitionerSignature({ 208→ accountId: account?.id, 209→ enabled: Boolean(noApptSignatureMode), 210→ }) 211→ 212→ const signatureUrl = noApptSignatureMode 213→ ? practitionerSignatureFromConsultation?.url ?? '' …(truncated, 16484 more chars) Tool results: → result (ok): Found 14 files packages/@doctolib/documents-ui/src/index.ts app/assets/javascripts/modules/doctor/mobile/document-upload/ConfirmationModule/useConfirmationModuleController.tsx packages/@doctolib/financial_os_fr_billing/src/components/doctor/desktop/patient-folder/body/tp-amc/uploadAmcDocument.ts engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts packages/@doctolib/documents-ui/src/data/queries/documents.ts packages/@doctolib/documents-ui/src/data/queries/documents …(truncated, 673 more chars) Let me look at buildDocumentEndpoint to understand how the v2 endpoint is selected: Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/documents-ui/src/helpers/buildDocumentEndpoint.ts Tools used: - Grep: invalidateQueries.decryptedDocuments|decryptedDocuments.invalidate in /Users/USER/workspace/doctolib/doctolib Tool results: → result (ok): 1→import { isFeatureEnabled } from '@doctolib/variation_management' 2→import type { FeatureSwitchOverride } from './types' 3→ 4→// Helper function to determine if V2 API should be used 5→export function shouldUseV2Api(featureSwitchOrOverride?: string | FeatureSwitchOverride): boolean { 6→ // Handle FeatureSwitchOverride object 7→ if (typeof featureSwitchOrOverride === 'object' && featureSwitchOrOverride !== null) { 8→ const override = featureSwitchOrOverri …(truncated, 1654 more chars) Tool results: → result (ok): No files found Perfect! Now let me check the DocumentImportContext to see how uploads are handled: Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/contexts/doctor/desktop/DocumentImportContext.tsx (1–400) Tool results: → result (ok): 1→import type { FunctionComponent, ReactNode } from 'react' 2→import { useCallback, useEffect, useMemo, useRef, useState } from 'react' 3→import { v4 as uuidv4 } from 'uuid' 4→import { isEmpty } from 'lodash-es' 5→// TODO - CAT : Fix useQueryClient usage in TT-20637 6→// eslint-disable-next-line doctolib-custom-rules/react-query/react-query-monolith-conventions,doctolib-custom-rules/react-query/restricted-imports 7→import { useQueryClient } from '@tanstack/react-que …(truncated, 18928 more chars) Perfect! Now let me check if there's anything about invalidating decryptedDocuments after upload. Let me search for the new documents-ui package queries: Tools used: - Read: /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/documents-ui/src/data/queries/documents.ts (1–250) 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, 10569 more chars) Perfect! Now I have a clear picture. Let me create a comprehensive report based on my findings. Summary of Document Import Issue - Exploration Results Based on my thorough exploration of the codebase, I've identified the complete flow and pinpointed the likely root cause of the document import issue. 1. UPLOAD_DOCUMENT_WRITE Feature Switch Usage Location: /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/documents-ui/src/constants.ts export const UPLOAD_DOCUMENT_FEATURE_SWITCH_OVERRIDE: FeatureSwitchOverride = { featureSwitch: 'upload_document_write', mode: 'double-write', } When the upload_document_write feature switch is enabled, it forces double-write mode using V2 API endpoints. 2. V2 Endpoints for Document Upload Locations: - /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/doctor_app/v2/documents_controller.rb - /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/doctor_app/v2/presigned_urls_controller.rb The V2 controller uses Documents::V3::DocumentService which performs: - Server-side encryption (SSE) uploads to documents_server_encrypted bucket - Optional Tanker encryption via tanker_encrypted_file parameter - Atomic 2-phase commit pattern for S3 + DB consistency Endpoint routing: /doctor_app/v2/documents (POST/PUT) vs /doctor_app/documents (V1) 3. V2 API Selection Logic Location: /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/documents-ui/src/helpers/buildDocumentEndpoint.ts export function shouldUseV2Api(featureSwitchOrOverride?: string | FeatureSwitchOverride): boolean { if (typeof featureSwitchOrOverride === 'object' && featureSwitchOrOverride !== null) { const override = featureSwitchOrOverride as FeatureSwitchOverride if (override.featureSwitch && isFeatureEnabled(override.featureSwitch)) { return true // ← When upload_document_write is enabled } return isFeatureEnabled('document_single_write') || isFeatureEnabled('document_double_write') } return isFeatureEnabled('document_single_write') || isFeatureEnabled('document_double_write') } export function buildDocumentEndpoint(documentId?: string, featureSwitchOverride?: string | FeatureSwitchOverride): string { const version = shouldUseV2Api(featureSwitchOverride) ? '/v2' : '' const documentPath = documentId ? /${documentId} : '' return /doctor_app${version}/documents${documentPath} // Results in /doctor_app/v2/documents } 4. Document Upload Flow Location: /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/contexts/doctor/desktop/DocumentImportContext.tsx The upload flow: 1. Files are processed in uploadFile() method (lines 294-395) 2. Encrypted form data is built with featureSwitchOverride: UPLOAD_DOCUMENT_FEATURE_SWITCH_OVERRIDE (line 321) 3. documentsCreate() mutation is called with the override (lines 335-342) 4. After successful upload, invalidateUnclassifiedDocuments() is called (line 396) 5. CRITICAL FINDING: Missing Cache Invalidation for decryptedDocuments The Root Cause: The decryptedDocuments query is NOT invalidated after document upload. Here's what is invalidated: File: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts // Lines 443-551: useDocumentsCreate() mutation export const useDocumentsCreate = () => { const buildEncryptedFormData = useEncryptedDocumentFormDataBuilder() return useMutation({ mutationFn: async (variables) => { // ... creates document via V2 endpoint return JSON.parse(responseBody) }, // ⚠️ NO onSuccess handler - this is the problem! }) } Comparison with useDocumentsUpdate (line 415-427): export const useDocumentsUpdate = () => { // ... return useMutation({ // ... onSuccess: (_, props) => { queryClient.invalidateQueries({ queryKey: buildDocumentsShowQueryKey(props.documentId) }) // ... invalidates patient and appointment documents }, }) } 6. What Should Be Invalidated After Upload Location: /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/decryptedDocuments.ts The decryptedDocuments query is built with: export const buildDecryptedDocumentQueryKey = (documentId?: string) => [ 'doctor_app', 'decrypted_documents_url', documentId, ] After creating a document via V2 API, this cache is never invalidated, which explains why: - Immediate viewing after upload fails (cached empty/stale data) - After 5 minutes (DOCUMENT_CACHE_TIME = 5 60 * 1000), the cache expires - After logout/login, cache is cleared 7. Where Imports Component Attempts to View Documents File: /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/Imports.tsx Lines 213-217: const { data: decryptedDocument } = useDecryptedDocumentsShow({ document: selectedDocumentFromContext && 'id' in selectedDocumentFromContext ? selectedDocumentFromContext : null, }) After upload succeeds, the document has an id but the decryptedDocument query returns stale/missing data because the cache was never invalidated. Root Cause Summary The useDocumentsCreate() mutation lacks an onSuccess callback to invalidate the decryptedDocuments query. When upload_document_write feature switch is enabled: 1. Document is successfully created via /doctor_app/v2/documents (POST) 2. Document has a valid id in the response 3. Imports component tries to view the document using useDecryptedDocumentsShow 4. The decryptedDocuments query cache for this new document ID is empty/stale 5. Viewing fails until cache expires (5 minutes) or user logs out/logs back in Recommended Fix The useDocumentsCreate() mutation should include cache invalidation in its onSuccess handler similar to useDocumentsUpdate(), specifically: - Invalidate buildDecryptedDocumentQueryKey(uploadedDocumentId) - Invalidate patient/appointment document indices - Consider invalidating any decrypted document URLs that may be cached