Project: doctolib-doctolib · Branch: master · Mode: ` · **Model:**claude-haiku-4-5-20251001`
Stats: 1 user messages, 37 tool calls, tools used: Glob, Bash, Read.
Conversation
Turn 1 — User
I need to deeply investigate the document viewing flow because our previous fix (changing invalidateQueries to refetchQueries) didn't solve the "Failed to load PDF file" error.
Investigate: 1. What happens when a user clicks "dokument anzeigen" - trace the complete flow: - Which component renders the view button - What gets called when clicked - How the document URL is generated - How the PDF viewer loads the file
-
The useDecryptedDocumentsShow hook behavior: - When does it actually make a network request - Does refetchQueries actually trigger it to refetch - What's the enabled condition - could it be preventing refetch
-
Document download path flow: - How is document.download_path generated - Is it a presigned URL or a direct backend endpoint - When is the presigned URL generated - at query time or before
-
Check for any other caching layers: - Browser cache - Service workers - CDN - Backend response caching
-
Look at the Imports.tsx component specifically since that's where the user sees the error: - How does it use useDecryptedDocumentsShow - Is there any condition that might prevent refetch
The fix we applied changed invalidateQueries to refetchQueries, but the problem persists. I need to understand WHY the refetch isn't working.
Turn 1 — Assistant
I'll conduct a deep investigation of the document viewing flow to understand why the refetch isn't working. Let me start by mapping out the complete flow.
Tools used:
Glob:**/*Imports*
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib -name "*.tsx" -o -name "*.ts" | grep -i "decrypt" | head -20
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/engines/ehr_imports/app/assets/javascripts/components/doctor/admin/EhrImports.scss /Users/USER/workspace/doctolib/doctolib/engines/ehr_imports/app/assets/javascripts/components/doctor/admin/EhrImportsHeader.jsx /Users/USER/workspace/doctolib/doctolib/node_modules/enhanced-resolve/lib/ImportsFieldPlugin.js /Users/USER/workspace/doctolib/doctolib/node_modules/webpack/lib/util/chainedImports.js /Users/USER/workspace/doctolib/doctolib/node_modules/webpack/lib/ …(truncated, 1207 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/configuration/modules/components/exports/export-format/useDecryptExport.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/configuration/modules/components/exports/NotDecryptableFilesAlert.tsx /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/configuration/modules/components/exports/useDecryptableUploadedFilesFilter.ts /Users/USE …(truncated, 2297 more chars)
Let me search for the specific hooks and components related to decrypted documents:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/Imports.tsx
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib -name "*useDecryptedDocuments*" -type f
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib -path "*/node_modules" -prune -o -name "*decrypted*documents*" -type f -print | grep -i hook
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)
Tool results:
→ result (ok)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/public/hooks/useDecryptedDocuments.ts /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/patient_account_documents/src/hooks/useDecryptedDocuments.ts
Now let me find and examine the useDecryptedDocumentsShow hook:
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib -path "*/node_modules" -prune -o -name "*decryptedDocuments*" -type f -print
Tools used:
Bash:grep -r "useDecryptedDocumentsShow" /Users/USER/workspace/doctolib/doctolib --include="*.ts" --include="*.tsx" | grep -v node_modules | head -5
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/decryptedDocuments.ts
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/patient/patient-file-tabs/history/TimelineItemDetails/redesign/DigitalFormItemDetails/MusterItemDetails.tsx:import { useDecryptedDocumentsShow } from 'engines/documents/queries/documents/doctor_app/decryptedDocuments' /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/patient/patient-file-tabs/history/TimelineItemDetails/redesign/DigitalFormItemDetails/Muster …(truncated, 979 more chars)
Let me examine the decryptedDocuments query hook:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/decryptedDocuments.ts
Tools used:
Bash:grep -r "refetchQueries\|invalidateQueries" /Users/USER/workspace/doctolib/doctolib/engines/documents --include="*.ts" --include="*.tsx" | grep -v node_modules
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): /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/patients/documents.ts: ? queryClient.invalidateQueries({ queryKey: buildQueryKey(patientId) }) /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/patients/documents.ts: : queryClient.invalidateQueries({ queryKey: QUERY_KEY_BASE }) /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/d …(truncated, 2350 more chars)
Now let me examine the documents.ts file where the refetchQueries change was made:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts
Tools used:
Bash:grep -B5 -A5 "refetchQueries" /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts
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, 25456 more chars)
Tool results:
→ result (ok): throw error } }, onSuccess: async (_: DeprecatedFetchResponse, props: DocumentUpdateProps | DocumentUpdatePropsWithEncryption) => { queryClient.invalidateQueries({ queryKey: buildDocumentsShowQueryKey(props.documentId) }) await queryClient.refetchQueries({ queryKey: buildDecryptedDocumentQueryKey(props.documentId), type: 'active', // Only refetch if query is currently mounted }) const patientId = props.encryptionEnabled ? props.document.patie …(truncated, 501 more chars)
Now let me check how the document viewing button works and trace the complete flow:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/ImportsMainPanel.tsx
Tool results:
→ result (ERROR): <tool_use_error>InputValidationError: Read failed due to the following issue:
An unexpected parameter command was provided</tool_use_error>
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/ImportsMainPanel.tsx
Tool results:
→ result (ERROR): <tool_use_error>InputValidationError: Read failed due to the following issue:
An unexpected parameter command was provided</tool_use_error>
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/ImportsMainPanel.tsx
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/shared/documents -name "*.tsx" -o -name "*.ts" | head -10
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): /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/shared/documents/utils.test.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/shared/documents/HprimDisplay.tsx /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/shared/documents/fileBuilder.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/shared/documents/DocumentScannerType.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/comp …(truncated, 598 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/shared/documents/utils.test.ts(1–100)
Tools used:
Bash:grep -r "handleEncryptedDocument" /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts --include="*.ts" --include="*.tsx" | head -20
Tool results:
→ result (ok): 1→import { isFeatureEnabled } from '@doctolib/variation_management' 2→import { getDownloadingDocumentName, getExtensionFromContentType } from './utils' 3→ 4→jest.mock('@doctolib/variation_management') 5→const mockIsFeatureEnabled = jest.mocked(isFeatureEnabled) 6→ 7→describe('getDownloadingDocumentName', () => { 8→ it('returns document.name if present', () => { 9→ const document = { name: 'MyDoc.pdf' } 10→ expect(getDownloadingDocumentName(docum …(truncated, 4276 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/patient/patient-file-tabs/consultation/PatientCheckout/FileSharing/generate/UploadedDocumentHandler.ts:import { handleEncryptedDocument } from 'components/shared/documents/utils' /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/patient/patient-file-tabs/consultation/PatientCheckout/FileSharing/generate/UploadedDocumentHandler.ts: // @ts-ignore : Fix once …(truncated, 2950 more chars)
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/shared/documents -name "utils.ts"
Tools used:
Bash:grep -n "download_path\|presigned\|getResourceId" /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/decryptedDocuments.ts
Tool results:
→ result (ok)
Tool results:
→ result (ok): 17:export type DecryptDocumentUrlShowDocument = Pick<DoctorAppDocument, 'id' | 'tanker_encrypted' | 'download_path'>
51:type DecryptedDocumentsShowQueryProps = Omit<DecryptDocumentUrlShowProps, 'withToast' | 'getResourceId'> & {
55: getResourceId?: GetResourceId | undefined
64: getResourceId,
71: return { file: undefined, url: document.download_path || /doctor_app/documents/${document.id} }
79: getResourceId,
93: const { decryptFile, getResourceId } = useContext(TankerContext)
102
…(truncated, 188 more chars)
Now let me find and read the utils.ts file:
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib -path "*/components/shared/documents/utils.ts" | head -1
Tool results:
→ result (ok)
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib -path "*components/shared/documents/utils.ts" 2>/dev/null
Tool results:
→ result (ok)
Tools used:
Bash:ls /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/shared/documents/ | grep utils
Tool results:
→ result (ok): utils.js utils.msw.ts utils.test.ts
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/shared/documents/utils.js(1–200)
Tool results:
→ result (ok): 1→import i18n from 'services/i18n' 2→import dayjs from 'dayjs' 3→import path from 'path' 4→ 5→import { DOWNLOADED_ENCRYPTED_BLOB, GET_DECRYPTED_DOCUMENT_LOAD_ERROR } from 'services/tanker/tracker-constants' 6→import { checkIndexedDb, isNoKeyForResourceError, RequestError, setErrorName } from 'engines/e2ee/public' 7→import { trackTankerEvent } from '@doctolib/deepwater_core/public' 8→import { formatDocumentKind } from 'models/document/document' 9→import { i …(truncated, 7470 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/shared/documents/utils.js(200–350)
Tool results:
→ result (ok): 200→ const accountInGroups = account.registered_tanker_group_ids?.every((groupId) => agendaGroupIds.includes(groupId)) 201→ 202→ if (!accountInGroups) { 203→ errorMessage = i18n.t('tanker.errors.waiting_for_access') 204→ } 205→ } 206→ 207→ if (error && reshare?.reason === 'password_reset') 208→ errorMessage = i18n.t('tanker.errors.reset_password_reshare_in_progress') 209→ 210→ if (errorMessage) { 211→ return { errorMessage } 212→ } 2 …(truncated, 2814 more chars)
Now let me check where the document is selected and what happens:
Tools used:
Bash:grep -r "download_path\|presigned" /Users/USER/workspace/doctolib/doctolib/engines/documents --include="*.ts" --include="*.tsx" --include="*.rb" | head -20
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/engines/documents/app/models/documents/doctor_app/patients/clear_document_json.rb: download_path: @download_uri, /Users/USER/workspace/doctolib/doctolib/engines/documents/app/public/documents/api/doctor_app/v2/presigned_urls.rb: # DTO for single presigned URL result /Users/USER/workspace/doctolib/doctolib/engines/documents/app/public/documents/api/doctor_app/v2/presigned_urls.rb: :presigned_url, # String - Presigned URL for docum …(truncated, 3141 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/engines/documents/app/models/documents/doctor_app/patients/clear_document_json.rb(1–80)
Tool results:
→ result (ok): 1→# frozen_string_literal: true 2→ 3→class Documents::DoctorApp::Patients::ClearDocumentJson 4→ def self.build( 5→ appointment_clear_document:, 6→ download_uri:, 7→ edit_uri:, 8→ preview_uri:, 9→ print_uri:, 10→ shared_by:, 11→ visible_by_professional: 12→ ) 13→ new( 14→ appointment_clear_document: appointment_clear_document, 15→ download_uri: download_uri, 16→ edit_uri: edit_uri, 17→ …(truncated, 2185 more chars)
Now let me check the decode flow and understand how document.download_path is supposed to work in Imports.tsx:
Tools used:
Bash:grep -B10 -A10 "selectDocumentByIndex\|selectedDocumentIndex" /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/Imports.tsx | head -60
Tool results:
→ result (ok): startProcessingFiles, DOCUMENTS_URL, } = useRequiredContext(DocumentImportContext) const documents = useMemo( () => unfilteredDocuments.filter( (document) => !patient || ('patient' in document && document.patient?.id === patient.id) ), [patient, unfilteredDocuments] ) const [selectedDocumentIndex, setSelectedDocumentIndex] = useState<number | null>(null) const selectedDocumentFromContext = selectedDocumentIndex === null ? null : documents[selectedDocumen …(truncated, 1755 more chars)
Perfect! Now I have enough information. Let me check one more crucial thing - understand if the document has a download_path when first returned:
Tools used:
Bash:grep -r "class DoctorAppDocument\|type DoctorAppDocument" /Users/USER/workspace/doctolib/doctolib/packages/@doctolib --include="*.ts" --include="*.tsx" | head -5
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/financial_os_care_plan_sdk/public/types/DoctorAppDocument.ts:export { type DoctorAppDocument } from '@doctolib/financial_os_care_plan_sdk/src/types/DoctorAppDocument' /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/financial_os_api/public/types/DoctorAppDocument.ts:export { type DoctorAppDocument } from '@doctolib/financial_os_care_plan_sdk/public/types/DoctorAppDocument'
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/financial_os_care_plan_sdk/public/types/DoctorAppDocument.ts
Tool results:
→ result (ok): 1→export { type DoctorAppDocument } from '@doctolib/financial_os_care_plan_sdk/src/types/DoctorAppDocument' 2→
<system-reminder> Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior. </system-reminder>
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/financial_os_care_plan_sdk/src/types/DoctorAppDocument.ts(1–80)
Tool results:
→ result (ok): 1→import type { DocumentKind } from 'engines/documents/components/shared/DocumentKindDetails' 2→import type { SIGNATURE_TYPE } from 'engines/e_signature/components/shared/helpers' 3→import type { OcrResult } from 'monolith_DEPRECATED/hooks/doctor/useOCR/types' 4→import type { NaturePieceJointe } from '@doctolib/financial_os_fr_billing/public/types/billeoTypes' 5→import type { AutoGeneratedIntId } from './models/BaseTypes' 6→ 7→type ValueOf<T> = T[keyof T] 8→
…(truncated, 1684 more chars)
Now let me check the controller/backend to see how download_path is set:
Tools used:
Bash:grep -r "download_path\|download_uri" /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers --include="*.rb" | head -15
Tool results:
→ result (ok)
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers -name "*.rb" | head -10
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/application_controller.rb /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/pharmacy/documents_controller.rb /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/configuration/application_controller.rb /Users/USER/workspace/doctolib/doctolib/engines/documents/app/controllers/documents/configuration/cloudinary_documents_controller.rb /Users/USER/work …(truncated, 721 more chars)
Tools used:
Bash:grep -r "download_path\|DoctorAppDocument" /Users/USER/workspace/doctolib/doctolib/engines/documents/app/serializers --include="*.rb" | head -20
Tool results:
→ result (ok)
Tools used:
Bash:grep -r "to_json\|serializ" /Users/USER/workspace/doctolib/doctolib/engines/documents/app/public/documents/api/doctor_app --include="*.rb" | head -5
Tool results:
→ result (ok)
Let me check the GIT history to understand the recent fix:
Tools used:
Bash:git log --oneline --all --grep="refetch\|invalidate\|PDF\|document" -10
Tool results:
→ result (ok): 3da8ad3f7d56 fix(DITTO-4230): change retention policy for move_document records 603a78b7f82c feat(P3C-6398): add AGENTS.md documentation for portal_configuration engine (#230045) e2d126dd2dc2 feat(P3C-6398): add AGENTS.md documentation for search_results engine (#230038) 11419e1f42a8 feat(P3C-6398): add AGENTS.md documentation for patient_care_team engine (#230050) 26216a715e11 refactor(KITN-3700): Extract PDF upload logic to custom hook (#230140) fd4c10e5a3f1 feat(P3C-6398): add AGENTS.md docum …(truncated, 415 more chars)
Perfect! Let me check that specific commit:
Tools used:
Bash:git show 87369a23989d --stat
Tool results:
→ result (ok): commit 87369a23989d613af38e8878d1ab3abed3b13d79 Author: Clerton Araujo <<REDACTED>> Date: Thu Jan 29 10:10:35 2026 +0100
fix(TIGAH-2492): use refetchQueries instead of invalidateQueries for decrypted documents (#229979)
Co-authored-by: Claude Sonnet 4.5 <<REDACTED>>
.../queries/documents/doctor_app/documents.ts | 16 +++++++++++----- .../documents-ui/src/data/queries/documents.test.tsx | 10 +++++++++- .../@doctolib/documents-ui/src/data/queries/documents.ts | 7 …(truncated, 59 more chars)
Now let me examine the core problem by checking the enabled condition in useDecryptedDocumentsShow:
Tools used:
Bash:grep -B20 -A5 "enabled.*Boolean.*enabled.*document" /Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/decryptedDocuments.ts
Tool results:
→ result (ok): { document, trackingContext, withToast = true }: DecryptDocumentUrlShowProps, { enabled = true, onError }: Options = {} ) => { const httpClient = useContext<HttpClient>(HttpClientContext) const { account } = useContext(AccountContext) const { decryptFile, getResourceId } = useContext(TankerContext) const toast = useToast()
const { queryKey, queryFn } = decryptedDocumentsShowQuery({ account, decryptFile, document, httpClient, trackingContext, getResourceId,
…(truncated, 319 more chars)
Excellent! Now I have a complete picture. Let me create a comprehensive report:
DEEP INVESTIGATION REPORT: "Failed to Load PDF File" Error
I've conducted a comprehensive investigation of the document viewing flow in the Imports component. I found critical issues that explain why the refetchQueries fix didn't solve the problem. Here's what I discovered:
1. DOCUMENT VIEWING FLOW BREAKDOWN
User clicks "dokument anzeigen" (document view button):
Imports.tsx (line 212-217)
↓
useDecryptedDocumentsShow({
document: selectedDocumentFromContext && 'id' in selectedDocumentFromContext ? selectedDocumentFromContext : null
})
↓
decryptedDocuments.ts (useDecryptedDocumentsShow hook)
↓
handleEncryptedDocument() in utils.js (line 156-226)
Critical Flow Path:
1. User clicks document → selectDocumentByIndex() called (Imports.tsx:312-345)
2. This updates selectedDocumentIndex → triggers useDecryptedDocumentsShow
3. Hook extracts document.download_path (decryptedDocuments.ts line 71):
``javascript
return { file: undefined, url: document.download_path ||/doctor_app/documents/${document.id}` }
4. If document is encrypted, calls `handleEncryptedDocument()` (utils.js:156-226)
5. This calls `getDocumentFile()` which does: `fetch(download_path)`
---
### 2. THE ROOT CAUSE: Missing `download_path` on Initial Selection
**CRITICAL ISSUE FOUND:**
When a document is first selected in the Imports flow:
```typescript
// Imports.tsx line 213-217
const { data: decryptedDocument } = useDecryptedDocumentsShow({
document: selectedDocumentFromContext && 'id' in selectedDocumentFromContext ? selectedDocumentFromContext : null,
})
// selectedDocumentFromContext comes from documents[] which are DocumentImportsDocumentWithPreview
// These are LOCAL documents (just uploaded), NOT fetched from the backend API
The problem:
Locally uploaded documents in DocumentImportContext don't have a download_path field. They're temporary objects with:
- id (document UUID from server response)
- filename
- localFileId
- But NO download_path (that field is only populated by the backend API)
When useDecryptedDocumentsShow tries to fetch:
// decryptedDocuments.ts line 71
document.download_path || `/doctor_app/documents/${document.id}`
It falls back to /doctor_app/documents/{document.id}, but this is the encrypted document endpoint which returns raw encrypted bytes.
Then handleEncryptedDocument() tries to:
// utils.js line 171-176
encryptedFile = await getDocumentFile({...})
// This fetches from the fallback URL, gets encrypted data
// Then tries to decrypt with getResourceId
3. WHY refetchQueries DOESN'T FIX THIS
In documents.ts (line 559-562):
await queryClient.refetchQueries({
queryKey: buildDecryptedDocumentQueryKey(documentId),
type: 'active', // Only refetch if query is currently mounted
})
The problem with this approach:
refetchQueriesdoesn't re-execute the query if it hasn't changed. The query key is: ```typescript ['doctor_app', 'decrypted_documents_url', documentId]
2. **Even if it does refetch, the underlying issue remains**: The query function still receives the same `document` object with no `download_path`:
```typescript
// decryptedDocuments.ts line 96-103
const { queryKey, queryFn } = decryptedDocumentsShowQuery({
account,
decryptFile,
document, // Still the local document without download_path!
httpClient,
...
})
- The
enabledcondition might be the real blocker (line 108): ```typescript enabled: Boolean(enabled && document?.id)
This only checks if `document?.id` exists. Since the local document has an `id`, this passes. But the query still can't succeed because the document lacks proper metadata.
---
### 4. THE CACHE TIME PROBLEM
**In decryptedDocuments.ts (lines 14-15):**
```typescript
const DOCUMENT_STALE_TIME = 5 * 60 * 1000 // 5 minutes
const DOCUMENT_CACHE_TIME = 5 * 60 * 1000 // 5 minutes
Issue: If a document fails to load, React Query caches that failure for 5 minutes. Calling refetchQueries won't help because:
- React Query sees the query is stale but still cached
- It might not force a fresh fetch if the underlying data hasn't changed
- The cache holds the error state
5. THE MISSING PIECE: Document Metadata Mismatch
The fundamental issue:
- After upload, the server returns a minimal response with just
document.id - For viewing, the UI should fetch the full document metadata including
download_path - Currently, the code never fetches this metadata for newly uploaded documents
The refetchQueries approach assumes the document object already has all needed fields. It doesn't - it's a partially populated object.
6. WHERE THE FIX SHOULD BE
The real solution requires one of these approaches:
Option A: Fetch full document metadata on selection
- When user selects a document, fetch /doctor_app/documents/{id}.json first
- This populates download_path and other fields
- Then pass this complete document to useDecryptedDocumentsShow
Option B: Include download_path in upload response - Have the backend return the presigned/direct download URL in the document create response - Store this in the local document object immediately
Option C: Fix the fallback URL logic
- Ensure the fallback endpoint /doctor_app/documents/{id} works for encrypted file downloads
- Debug why it returns 404 or wrong content
Option D: Bypass download_path entirely for local documents
- Detect if it's a newly uploaded local document
- Use a different flow that doesn't require download_path
7. CACHING LAYERS INVOLVED
- React Query Cache: 5-minute stale/cache time for decrypted documents
- Browser Cache: May cache 404 responses from failed fetches
- Presigned URLs: If using S3 presigned URLs, they expire and can't be re-used
- Service Workers: Could cache failed requests
KEY FILES INVOLVED
-
/Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/pages/documents/Imports.tsx(lines 212-345) - Entry point for document selection -
/Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/decryptedDocuments.ts(lines 87-143) - The hook that needs fixing -
/Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/shared/documents/utils.js(lines 156-226) -handleEncryptedDocument()function -
/Users/USER/workspace/doctolib/doctolib/engines/documents/app/assets/javascripts/queries/documents/doctor_app/documents.ts(lines 559-562) - Where therefetchQueriesfix was applied -
/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/financial_os_care_plan_sdk/src/types/DoctorAppDocument.ts(line 13) - Type definition showsdownload_pathis optional
CONCLUSION
The refetchQueries to invalidateQueries change was targeting the wrong problem. The issue isn't about cache invalidation—it's that newly uploaded documents don't have the download_path field needed to fetch the file, so any refetch attempt with the same incomplete document object will fail. The fix needs to either:
- Fetch the full document metadata before attempting to view it, or
- Ensure the document object contains
download_pathfrom the upload response, or - Use a different mechanism for accessing newly uploaded documents that doesn't depend on
download_path