Project: workspace-doctolib · Branch: HEAD · Mode: ` · **Model:**claude-opus-4-7`
Stats: 1 user messages, 38 tool calls, tools used: Read, Bash.
Conversation
Turn 1 — User
Review the code changes in the treatment-plan repository PR #3084 (branch fhir-2221_display-last-dmp-access-date).
The diff is in these 3 files at /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/:
packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx— core fix: removedlastAccessDatefrom constructor params, now resolved at render time fromctx.patientusing a localDmpPatientInfocastpackages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx— TDD updates: addedlastDmpAccessDateto ctx, removedlastAccessDatefrom factory calls, added fallback testpackages/tpf-national-health-records-ui/src/components/NhrDocumentListFilterPanel/NhrDocumentListFilterPanel.stories.tsx— removedlastAccessDatefrom stories factory call
Please read these files in full and review for: correctness, adherence to project conventions (see CLAUDE.md and AGENTS.md in the repo root), TypeScript quality, and any issues. The CLAUDE.md is at /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/CLAUDE.md and AGENTS.md at the same level.
Turn 1 — Assistant
I'll review the code changes in the treatment-plan PR. Let me start by reading the relevant files in parallel.
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/CLAUDE.md
Tool results:
→ result (ok): 1 # CLAUDE.md
2
3 This is a full-stack healthcare treatment plan application (Kotlin/Spring Boot backend + React/TypeScript frontend) built as a modular monolith using the "hive" pattern with hexagonal architecture. It handles prescription creation, document management, and clinical workflows for the French and German markets.
4
5 Refer to @AGENTS.md for complete project overview, architecture, tech stack, and development guidelines.
6
7 ## Language Rule
8
9 All Claude stack files (C
…(truncated, 664 more chars)
Now let me read the three files in question.
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx
Tool results:
→ result (ok): 1 import { formatNhrDateTime } from '#utils/nhrDateHelpers'
2 import type { Option } from '@doctolib/design-system/Option'
3
4 import {
5 type DateMode,
6 NhrDateSelectionFilter,
7 } from '../../../components/NhrDateSelectionFilter/NhrDateSelectionFilter'
8 import type { FilterConfig, NhrPatientInfo } from '../../../types'
9
10 type DmpPatientInfo = NhrPatientInfo & { lastDmpAccessDate?: string }
11
12 export const createNhrDateSelectionFilterConfig = ({
13 label,
14 dateTypeOptions =
…(truncated, 1600 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx
Tool results:
→ result (ok): 1 import { t } from '#i18n'
2 import { formatNhrDateTime } from '#utils/nhrDateHelpers'
3 import { setup } from '@doctolib/test-helpers'
4 import { screen } from '@testing-library/react'
5 import { describe, expect, it, vi } from 'vitest'
6
7 import { mockPatientInfo } from '../../../test-helpers'
8 import { createNhrDateSelectionFilterConfig } from './createNhrDateSelectionFilterConfig'
9
10 const LAST_ACCESS_DATE_ISO = '2025-03-15T00:00:00'
11
12 const ctx = { patient: { ...mockPatientInfo,
…(truncated, 5116 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui/src/components/NhrDocumentListFilterPanel/NhrDocumentListFilterPanel.stories.tsx
Tool results:
→ result (ok): 1 import type { Meta, StoryObj } from '@storybook/react'
2 import { useState } from 'react'
3
4 import { createNhrDateSelectionFilterConfig } from '../../business/factories/filters/createNhrDateSelectionFilterConfig'
5 import { createNhrDocumentCategoryFilterConfig } from '../../business/factories/filters/createNhrDocumentCategoryFilterConfig'
6 import { createNhrDocumentStatusFilterConfig } from '../../business/factories/filters/createNhrDocumentStatusFilterConfig'
7 import { createNhrDocument
…(truncated, 4298 more chars)
Now let me look at related types to understand the context better — specifically NhrPatientInfo, FilterConfig, and mockPatientInfo.
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && git status
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && git diff HEAD~1 HEAD --stat 2>/dev/null || git log --oneline -5
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && find packages/tpf-national-health-records-ui -name "types.ts" -o -name "types.tsx" -o -name "i …(truncated, 19 more chars)
Tool results:
→ result (ok): On branch fhir-2221_display-last-dmp-access-date Your branch is up to date with 'origin/fhir-2221_display-last-dmp-access-date'.
Untracked files: (use "git add <file>..." to include in what will be committed) node_modules
nothing added to commit but untracked files present (use "git add" to track)
Tool results:
→ result (ok): .../createNhrDateSelectionFilterConfig.test.tsx | 27 ++++++++++++++++++---- .../filters/createNhrDateSelectionFilterConfig.tsx | 12 ++++++---- .../NhrDocumentListFilterPanel.stories.tsx | 1 - 3 files changed, 29 insertions(+), 11 deletions(-)
Tool results:
→ result (ok): packages/tpf-national-health-records-ui/src/types.ts packages/tpf-national-health-records-ui/src/index.ts packages/tpf-national-health-records-ui/src/test-helpers/index.ts packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/index.ts packages/tpf-national-health-records-ui/src/components/NhrDocumentCategoryFilter/index.ts packages/tpf-national-health-records-ui/src/test-helpers/fixtures/index.ts packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrShare …(truncated, 610 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui/src/types.ts
Tool results:
→ result (ok): 1 import type { DesignSystemIconComponent } from '@doctolib/design-system-icons'
2 import type { NhrDocument, NhrResultStatus } from '@doctolib/tpf-national-health-records'
3 import type { ReactNode } from 'react'
4
5 export type NhrPatientInfo = {
6 fullName: string
7 gender: 'male' | 'female' | 'other'
8 birthDate: string
9 age: number
10 }
11
12 export type ColumnConfig = {
13 key: string
14 header: string
15 sortable: boolean
16 /** Only required when sortable: true. Return
…(truncated, 3703 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && git diff HEAD~1 HEAD -- 'packages/tpf-national-health-records-ui/src/business/factories/filter …(truncated, 281 more chars)
Tool results:
→ result (ok): diff --git a/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx b/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx index 1325093d3..4467c780b 100644 --- a/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx +++ b/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionF …(truncated, 5553 more chars)
Now let me check what formatNhrDateTime does and check how the NhrDateSelectionFilter consumes this.
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && find packages/tpf-national-health-records-ui/src -name "nhrDateHelpers*"
Tool results:
→ result (ok): packages/tpf-national-health-records-ui/src/utils/nhrDateHelpers.ts packages/tpf-national-health-records-ui/src/utils/nhrDateHelpers.test.ts
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && grep -rn "lastAccessDate" packages/tpf-national-health-records-ui/src --include="*.ts" --inclu …(truncated, 21 more chars)
Tool results:
→ result (ok): packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx:34: const lastAccessDate = formatNhrDateTime(dmpPatient.lastDmpAccessDate ?? new Date().toISOString()) packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx:45: lastAccessDate={lastAccessDate} packages/tpf-national-health-records-ui/src/components/NhrDateSelectionFilter/NhrDateSelectionFilter.stories.tsx:47: lastAcces …(truncated, 790 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && grep -rn "lastDmpAccessDate" packages/ --include="*.ts" --include="*.tsx" | head -30
Tool results:
→ result (ok): packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx:10:type DmpPatientInfo = NhrPatientInfo & { lastDmpAccessDate?: string } packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx:34: const lastAccessDate = formatNhrDateTime(dmpPatient.lastDmpAccessDate ?? new Date().toISOString()) packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterCo …(truncated, 306 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui/src/utils/nhrDateHelpers.ts
Tool results:
→ result (ok): 1 const FALLBACK = '–'
2
3 export const formatNhrDate = (isoDate: string | null | undefined): string => {
4 if (!isoDate) return FALLBACK
5 const date = new Date(isoDate)
6 return Number.isNaN(date.getTime()) ? FALLBACK : date.toLocaleDateString()
7 }
8
9 export const formatNhrDateTime = (isoDate: string | null | undefined): string => {
10 if (!isoDate) return FALLBACK
11 const date = new Date(isoDate)
12 return Number.isNaN(date.getTime())
13 ? FALLBACK
14 : date.toLocaleS
…(truncated, 207 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && grep -n "DmpPatientInfo\|DmpServerFilters" packages/tpf-national-health-records-ui/src -r --in …(truncated, 30 more chars)
Tool results:
→ result (ok): packages/tpf-national-health-records-ui/src/types.ts:51: /* generic — DMP variant casts to DmpServerFilters / packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx:10:type DmpPatientInfo = NhrPatientInfo & { lastDmpAccessDate?: string } packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx:33: const dmpPatient = ctx.patient as DmpPatientInfo
Now let me check the test-helpers and see how the fallback test asserts.
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && cat packages/tpf-national-health-records-ui/src/test-helpers/index.ts
Tool results:
→ result (ok): export * from './fixtures'
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && grep -rn "mockPatientInfo" packages/tpf-national-health-records-ui/src/test-helpers/
Tool results:
→ result (ok): packages/tpf-national-health-records-ui/src/test-helpers/fixtures/mockData.ts:6:export const mockPatientInfo = nhrPatientInfoFixtureBuilder() packages/tpf-national-health-records-ui/src/test-helpers/fixtures/mockData.test.ts:3:import { mockColumns, mockPatientInfo, mockRows, nhrDocumentFixtureBuilder } from '../index' packages/tpf-national-health-records-ui/src/test-helpers/fixtures/mockData.test.ts:5:describe('mockPatientInfo', () => { packages/tpf-national-health-records-ui/src/test-helpers/fi …(truncated, 491 more chars)
Now let me check how createNhrDateSelectionFilterConfig is used in the calling app to understand if there's a separate caller that needs to also pass the patient.
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && grep -rn "createNhrDateSelectionFilterConfig" packages/ --include="*.ts" --include="*.tsx"
Tool results:
→ result (ok): packages/tpf-national-health-records-ui/src/index.ts:40:export { createNhrDateSelectionFilterConfig } from './business/factories/filters/createNhrDateSelectionFilterConfig' packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx:12:export const createNhrDateSelectionFilterConfig = ({ packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx:8:import { createNhrDateSelectionFilterConfig …(truncated, 1134 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && grep -rn "lastDmpAccessDate\|DmpPatient" packages/ --include="*.ts" --include="*.tsx" | head - …(truncated, 2 more chars)
Tool results:
→ result (ok): packages/tpf-nhr-fr/.storybook/preview.tsx:7:import { DmpPatientFileContextProvider } from '../src/hooks' packages/tpf-nhr-fr/.storybook/preview.tsx:56: <DmpPatientFileContextProvider> packages/tpf-nhr-fr/.storybook/preview.tsx:59: </DmpPatientFileContextProvider> packages/tpf-nhr-fr/src/business/consultation-consent-workflow/context.ts:25: * the orchestration layer to project into DmpPatientFileContext. packages/tpf-nhr-fr/src/components/dev-toolbar/dev-toolbar.test.tsx:8:import t …(truncated, 3626 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-nhr-fr/src/hooks/dmp-patient-file-context-types.ts
Tool results:
→ result (ok): 1 /*
2 * Types for the DmpPatientFileContext — session-scoped DMP state shared
3 * across DMP features within a patient file.
4 *
5 * These types are defined independently from the workflow layer's types
6 * (business/consultation-consent-workflow) so hooks/ never imports from
7 * business/. Structural compatibility is enforced at compile time where
8 * the two layers meet (the orchestration hook).
9 /
10
11 // --------------------------------------------------------------------------
…(truncated, 3859 more chars)
Now I have enough context. Let me also verify the formatNhrDateTime returns when called with today's date so I can verify the new test assertion of not.toContain('–').
Looking at formatNhrDateTime:
- When called with new Date().toISOString() it returns a valid formatted date string (NOT the FALLBACK '–').
- The test asserts paragraph.textContent).not.toContain('–') — this is correct because the today fallback produces a real date string.
Now let me confirm the file I'm reviewing and consolidate the findings. Let me check one more issue — paragraph.textContent could be null:
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx(73–83)
Tool results:
→ result (ok): 73 74 const paragraph = screen.getByText( 75 (_, el) => 76 el?.tagName === 'P' && 77 el.textContent.startsWith(t('tpf_national_health_records_ui.filters.date_selection.last_access.message')) 78 ) 79 expect(paragraph).toBeInTheDocument() 80 expect(paragraph.textContent).not.toContain('–') 81 }) 82
Notice that on line 77, el.textContent.startsWith(...) is called, but el.textContent is typed as string | null per DOM types. This could fail strict TypeScript checking. Let me see if the existing test code has the same pattern (it does on line 56-59 too) — so this was a pre-existing pattern. Let me confirm:
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && grep -n "textContent" packages/tpf-national-health-records-ui/src/business/factories/filters/c …(truncated, 42 more chars)
Tool results:
→ result (ok): 58: el.textContent === 77: el.textContent.startsWith(t('tpf_national_health_records_ui.filters.date_selection.last_access.message')) 80: expect(paragraph.textContent).not.toContain('–')
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && cat packages/tpf-national-health-records-ui/tsconfig.json 2>/dev/null | head -40
Tool results:
→ result (ok): { "extends": "../../tsconfig-base", "include": [ "../../vitest-shared.config.ts", "../../vitest-setup", "/*", "src/locales//.json", "package.json", "../../.storybook/.ts" ], "compilerOptions": { "types": ["vitest/globals", "@testing-library/jest-dom"] } }
Line 58 uses el.textContent === which works with null. Line 77 calls .startsWith() on possibly-null — that's a real type safety issue, but only if strictNullChecks is enabled. Let me check:
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && cat tsconfig-base.json 2>/dev/null | grep -E "strict|noImplicitAny"
Tool results:
→ result (ok): (Bash completed with no output)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && find . -name "tsconfig-base*" -maxdepth 3 -not -path "*/node_modules/*" 2>/dev/null
Tool results:
→ result (ok): ./tsconfig-base.json
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/tsconfig-base.json
Tool results:
→ result (ok): 1 {
2 "extends": ["@doctolib/upfront-dev/tsconfig.json"],
3 "compilerOptions": {
4 // Configuration for TS project references
5 "rootDir": ".",
6 "outDir": "build",
7 "composite": true,
8 "noEmit": false,
9 "declaration": true,
10 // need to set this back to false to allow noEmit: false
11 "allowImportingTsExtensions": false,
12 // was needed for webpack build
13 "jsx": "react-jsx",
14
15 // ignore errors due to design system
16 "exactOptional
…(truncated, 758 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && find node_modules/@doctolib/upfront-dev -name "tsconfig.json" 2>/dev/null | head -3
Tool results:
→ result (ok): node_modules/@doctolib/upfront-dev/tsconfig.json node_modules/@doctolib/upfront-dev/bases/tsconfig.json
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/node_modules/@doctolib/upfront-dev/tsconfig.json
Tool results:
→ result (ok): 1 { 2 "extends": ["./bases/tsconfig.json"], 3 "include": ["src", "tests", "*.ts"] 4 } 5
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/node_modules/@doctolib/upfront-dev/bases/tsconfig.json
Tool results:
→ result (ok): 1 // A core TS configuration for UpFront packages
2 //
3 // @see https://www.typescriptlang.org/tsconfig
4 {
5 // Let's start off strong. Includes strict: true.
6 "extends": ["@tsconfig/recommended/tsconfig.json"],
7 // Now let's tweak and augment that.
8 "compilerOptions": {
9 // Enable completion and type-safety for CSS modules (CSS/SCSS)
10 // Works hand in hand with asset typings (which we provide in assets.d.ts)
11 "plugins": [
12 {
13 "name": "typescrip
…(truncated, 2449 more chars)
The base config inherits strict: true. With strictNullChecks, el.textContent.startsWith(...) would be a type error since textContent is string | null. Let me verify the type-check actually catches this — first by looking at how the existing test (untouched) does this:
Looking at the original test at line 56-59, el.textContent === ${...} uses strict equality with a string, which works fine with null. But the new test at line 77 calls .startsWith() on el.textContent directly without null-checking. This is a TypeScript error under strict mode.
Let me run the type-check to see:
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui && timeout 90 npx tsc --noEmit 2>&1 | head -40
Tool results:
→ result (ok): timeout: failed to run command ‘npx’: No such file or directory
Tools used:
Bash:source ~/.zshrc 2>/dev/null; cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui && which npx node && node -- …(truncated, 7 more chars)
Tool results:
→ result (ok): /Users/USER/.local/share/mise/installs/npm/11.11.0/bin/npx /Users/USER/.local/share/mise/installs/node/24.14.1/bin/node v24.14.1
Tools used:
Bash:source ~/.zshrc 2>/dev/null; cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui && /Users/USER/.local/s …(truncated, 128 more chars)
Tool results:
→ result (ok): rsbuild.config.ts(1,10): error TS2305: Module '"@doctolib/upfront-dev/module-federation"' has no exported member 'createProducerRsbuildConfig'.
Tools used:
Bash:source ~/.zshrc 2>/dev/null; cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui && /Users/USER/.local/s …(truncated, 72 more chars)
Tool results:
→ result (ok): rsbuild.config.ts(1,10): error TS2305: Module '"@doctolib/upfront-dev/module-federation"' has no exported member 'createProducerRsbuildConfig'.
The TS check ran with only one unrelated rsbuild error — and crucially, NO errors from the test file. So el.textContent.startsWith is somehow accepted. Let me look more carefully — this is because el (in the matcher callback) might be typed loosely by getByText's matcher signature, which gives HTMLElement | null for el. After el?.tagName === 'P' && short-circuit, el is narrowed, but el.textContent is still string | null.
But tsc says no errors. Let me check the testing-library type for MatcherFunction:
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && grep -A 5 "MatcherFunction" node_modules/@testing-library/dom/types/matches.d.ts 2>/dev/null | …(truncated, 9 more chars)
Tool results:
→ result (ok): export type MatcherFunction = ( content: string, element: Element | null, ) => boolean export type Matcher = MatcherFunction | RegExp | number | string
// Get autocomplete for ARIARole union types, while still supporting another string // Ref: https://github.com/microsoft/TypeScript/issues/29729#issuecomment-567871939 export type ByRoleMatcher = ARIARole | (string & {})
Right — element is Element | null. So narrowing via el?.tagName === 'P' confirms el is non-null (the ?. produces string | undefined and only 'P' matches when defined). But Element.textContent is string | null. So el.textContent.startsWith(...) should be a type error.
Let me run tsc just on the test file specifically to confirm:
Tools used:
Bash:source ~/.zshrc 2>/dev/null; cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && /Users/USER/.local/share/mise/installs/npm/11.11.0/bin/npx t …(truncated, 264 more chars)
Tool results:
→ result (ok): packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx(45,7): error TS2339: Property 'toBeChecked' does not exist on type 'Assertion<HTMLElement>'. packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx(53,7): error TS2339: Property 'toBeChecked' does not exist on type 'Assertion<HTMLElement>'. packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDate …(truncated, 1144 more chars)
The errors above are due to my standalone invocation lacking proper project setup (jest-dom types, etc.), but the key observation is that no errors related to textContent.startsWith are flagged by the standalone strict tsc — interesting. Let me check more carefully — running with strictNullChecks, the type of el.textContent would be string | null. Let me explicitly test:
Tools used:
Bash:source ~/.zshrc 2>/dev/null; cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && /Users/USER/.local/share/mise/installs/npm/11.11.0/bin/npx t …(truncated, 310 more chars)
Tool results:
→ result (ok): packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx(97,42): error TS2339: Property 'toHaveTextContent' does not exist on type 'Assertion<HTMLElement>'.
Hmm, no textContent error. Let me check by isolating: is element.textContent indeed typed as string | null?
Actually, looking more carefully, screen.getByText with a matcher function will iterate using node.textContent. The matcher function callback's element parameter is Element | null. After el?.tagName === 'P', el is narrowed to Element (non-null). Then el.textContent is indeed string | null per DOM lib. Calling .startsWith on it should error.
But the test passes type-check on the project level. Let me check if maybe lib types differ. Actually lib.dom.d.ts shows Node.textContent: string | null. Let me check the existing el.textContent === pattern (line 56-58) which the new test mimics — perhaps somewhere there's a wider type that loosens it.
Let me check the actual file behavior:
Tools used:
Bash:source ~/.zshrc 2>/dev/null; cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui && /Users/USER/.local/s …(truncated, 139 more chars)
Tool results:
→ result (ok): rsbuild.config.ts(1,10): error TS2305: Module '"@doctolib/upfront-dev/module-federation"' has no exported member 'createProducerRsbuildConfig'.
The package's full tsc check passes (only unrelated rsbuild error). So el.textContent.startsWith is somehow acceptable. Looking at the upfront base config, noUncheckedIndexedAccess: true is enabled but doesn't affect this. Maybe Element.textContent is narrowed differently in the jest-dom types or because we test against el?.tagName === 'P'.
Actually wait — in TypeScript strict mode, node.textContent on a Node is indeed string | null. But for Element interface specifically, maybe not. Let me move on — since tsc passes at project level, this is not a real type error. Let me run the actual tests to verify behavior:
Tools used:
Bash:source ~/.zshrc 2>/dev/null; cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && /Users/USER/.local/share/mise/installs/npm/11.11.0/bin/npm r …(truncated, 119 more chars)
Tool results:
→ result (ok): RUN v3.2.4 /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui Running tests with seed "1777385158783"
stdout | src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx Loading namespace: tpf_national_health_records_ui for language: en
✓ |@doctolib/tpf-national-health-records-ui| src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx > createNhrDateSelectionFilterCon …(truncated, 2494 more chars)
All 11 tests pass. Let me also check linting:
Tools used:
Bash:source ~/.zshrc 2>/dev/null; cd /Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date && /Users/USER/.local/share/mise/installs/npm/11.11.0/bin/npx e …(truncated, 367 more chars)
Tool results:
→ result (ok): Multiple projects found, consider using a single tsconfig with references to speed up, or use noWarnOnMultipleProjects to suppress this warning
Lint passes cleanly. Now I have all the data needed for a thorough review. Let me synthesize my findings.
Code Review: PR #3084 — fhir-2221_display-last-dmp-access-date
Files reviewed
/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.test.tsx/Users/USER/workspace/doctolib/worktrees/treatment-plan/fhir-2221_display-last-dmp-access-date/packages/tpf-national-health-records-ui/src/components/NhrDocumentListFilterPanel/NhrDocumentListFilterPanel.stories.tsx
Verification performed
- TypeScript type-check on the package: passes (only an unrelated pre-existing rsbuild error).
- ESLint on the three changed files: clean.
- Vitest run for the impacted test file: 11/11 tests pass, including the new fallback test.
Important issues (confidence 80-89)
1. "Today as fallback" semantics are misleading and not aligned with the AC contract — confidence 85
- File: packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx:34
- Snippet: const lastAccessDate = formatNhrDateTime(dmpPatient.lastDmpAccessDate ?? new Date().toISOString())
- Problem: When the patient has no lastDmpAccessDate, the UI silently advertises "today" as the last DMP access date. That is a false statement to a clinician — there has been no access. The mainline formatNhrDateTime already returns the FALLBACK character '–' for null/undefined, so the natural elegant fix is to pass dmpPatient.lastDmpAccessDate directly and let formatNhrDateTime show '–' (which is what it was designed for). Alternatively, hide the line / show an explicit "never accessed" message. The new test even encodes this misleading behavior by asserting not.toContain('–'), locking in the wrong semantic.
- Suggested fix:
```tsx
const lastAccessDate = formatNhrDateTime(dmpPatient.lastDmpAccessDate)
And update the fallback test to assert the dash is shown (or a localized "never accessed" message), matching what `formatNhrDateTime` already does for nullish input.
**2. Unsafe `as` cast on a context-supplied value — confidence 80**
- File: `packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDateSelectionFilterConfig.tsx:10,33`
- Code:
```ts
type DmpPatientInfo = NhrPatientInfo & { lastDmpAccessDate?: string }
...
const dmpPatient = ctx.patient as DmpPatientInfo
- Problem: The cast is documented in
types.tsas the country-variant pattern (/** Passed to every FilterConfig.render() — country variant casts to its own patient type */), so the pattern itself is project convention. However,DmpPatientInfois duplicated locally in this file even thoughtpf-nhr-fris the consumer that actually owns DMP types (e.g.DmpPatientFileContextValue). That increases drift risk: if the FR package later defines a canonicalDmpPatientInfo(or extendsNhrPatientInfo), this local type silently desynchronizes. Also, sincelastDmpAccessDateis the only DMP-specific field used here and it is optional, a narrower in-file type ({ lastDmpAccessDate?: string }) and a simple property access onctx.patientwould be just as safe and avoids implying a shared shape that doesn't exist. - Suggested fix: either (a) put
DmpPatientInfoin the sharedtypes.tsso all DMP-aware factories agree on its shape, or (b) drop the alias and read the optional field directly: ```ts const lastDmpAccessDate = (ctx.patient as { lastDmpAccessDate?: string }).lastDmpAccessDate
``` Either form removes the appearance of a structural contract that isn't enforced anywhere.
Nitpicks below threshold (not blocking, surfacing for awareness only)
- The fallback test asserts
not.toContain('–')to prove "today" was rendered. It is an indirect assertion — checking the absence of the FALLBACK glyph is fragile if the formatter ever changes the FALLBACK character. A direct assertion against a stub (e.g.vi.useFakeTimers().setSystemTime(...)or asserting againstformatNhrDateTime(new Date().toISOString())) would tie the test to behavior, not to a sentinel string. Confidence: 60. paragraph.textContent.startsWith(...)on line 77 of the test is technicallystring | null. The package's full tsc passes, so this is not a real type error in practice, but the existing pattern at line 56-58 usesel.textContent === ...(which is null-safe). Mirroring that style would keep the file internally consistent. Confidence: 35.
What looks good
- The refactor is correctly TDD-driven and small in scope.
- Resolving
lastAccessDateat render time fromctx.patientis the right architectural move — the previous design forced the caller (e.g.tpf-nhr-fr) to thread DMP-specific data through a generic factory, which violated the FilterConfig contract documented intypes.ts. - The story update correctly drops the now-removed prop.
- Tests pass and ESLint is clean. No
eslint-disable, noas any.
Net: the structural change is a clear improvement; my main concern is the silent "today" fallback (Important #1) — that is a UX/correctness call that should be revisited with the product/UX owner before merge, because formatNhrDateTime already encodes the right behavior for missing dates.