Project: workspace-doctolib · Branch: HEAD · Mode: ` · **Model:**claude-sonnet-4-6`
Stats: 1 user messages, 37 tool calls, tools used: Bash, Read.
Conversation
Turn 1 — User
Review the frontend architecture of the PR on branch fhir-2219_implement-multi-select-category-filter in the treatment-plan repo at /Users/USER/workspace/doctolib/treatment-plan.
Run git diff origin/main...HEAD to see all changes. Also read the relevant source files directly.
Context
This PR adds a custom MultiSelectDropdown component in packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/. The directory contains:
- MultiSelectDropdown.tsx — orchestrator (state + filtering logic)
- MultiSelectDropdownTrigger.tsx — combobox input + clear button + chevron icon
- MultiSelectDropdownPopup.tsx — fieldset/dialog with legend
- MultiSelectDropdownLabel.tsx — label sub-component
- MultiSelectDropdownOptions.tsx — checkbox list sub-component
- hooks.ts — useDropdownClose (click-outside + Escape)
- helpers.ts — normalize (diacritic stripping)
- index.ts — only exports MultiSelectDropdown
The component is then consumed by:
- NhrDocumentCategoryFilter.tsx (thin wrapper re-exporting props)
- createNhrDocumentCategoryFilterConfig.tsx (factory)
Check for compliance with UpFront architecture patterns: separation of concerns, naming conventions, component structure, barrel exports, hook placement, helper placement, presentational vs. container split, and any anti-patterns. Report specific violations with file and line references where possible.
Turn 1 — Assistant
I'll start by examining the git diff and the relevant source files in parallel.
Tools used:
Bash:git diff origin/main...HEAD --name-only
Tool results:
→ result (ok): packages/tpf-national-health-records-ui/package.json packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDocumentCategoryFilterConfig.test.tsx packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDocumentCategoryFilterConfig.tsx packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/MultiSelectDropdown.stories.tsx packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/MultiSelectDropdown.test.tsx packa …(truncated, 1888 more chars)
Tools used:
Bash:ls /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/
Tool results:
→ result (ok): helpers.test.ts helpers.ts hooks.test.tsx hooks.ts index.ts MultiSelectDropdown.stories.tsx MultiSelectDropdown.test.tsx MultiSelectDropdown.tsx MultiSelectDropdownLabel.test.tsx MultiSelectDropdownLabel.tsx MultiSelectDropdownOptions.test.tsx MultiSelectDropdownOptions.tsx MultiSelectDropdownPopup.test.tsx MultiSelectDropdownPopup.tsx MultiSelectDropdownTrigger.test.tsx MultiSelectDropdownTrigger.tsx
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/MultiSelectDropdown.tsx
Tool results:
→ result (ok): 1 import type { Option } from '@doctolib/design-system/Option'
2 import { useId, useMemo, useRef, useState } from 'react'
3
4 import { MultiSelectDropdownLabel } from './MultiSelectDropdownLabel'
5 import { MultiSelectDropdownPopup } from './MultiSelectDropdownPopup'
6 import { MultiSelectDropdownTrigger } from './MultiSelectDropdownTrigger'
7 import { normalize } from './helpers'
8 import { useDropdownClose } from './hooks'
9
10 type MultiSelectDropdownProps = {
11 label: string
12 values
…(truncated, 2252 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/hooks.ts
Tool results:
→ result (ok): 1 import { type RefObject, useEffect, useRef } from 'react'
2
3 export const useDropdownClose = (
4 containerRef: RefObject<HTMLDivElement | null>,
5 onClose: () => void,
6 isOpen: boolean
7 ) => {
8 const onCloseRef = useRef(onClose)
9 // Sync ref so the stable event listeners always call the latest onClose.
10 useEffect(() => {
11 onCloseRef.current = onClose
12 }, [onClose])
13
14 useEffect(() => {
15 if (!isOpen) return undefined
16
17 const handlePointerDown =
…(truncated, 625 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/helpers.ts
Tool results:
→ result (ok): 1 export const normalize = (s: string) => 2 s 3 .normalize('NFD') 4 .replace(/\p{Diacritic}/gu, '') 5 .toLowerCase() 6
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/index.ts
Tool results:
→ result (ok): 1 export * from './MultiSelectDropdown' 2
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/MultiSelectDropdownTrigger.tsx
Tool results:
→ result (ok): 1 import { ChevronDownRegularIcon } from '@doctolib/design-system-icons/ChevronDownRegularIcon'
2 import { IconButton } from '@doctolib/design-system/IconButton/v2'
3
4 type MultiSelectDropdownTriggerProps = {
5 triggerId: string
6 popupId: string
7 isOpen: boolean
8 displayLabel: string
9 searchQuery: string
10 placeholder?: string
11 hasValues: boolean
12 onInputChange: (value: string) => void
13 onInputClick: () => void
14 onClear: () => void
15 }
16
17 export const Mult
…(truncated, 1459 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/MultiSelectDropdownPopup.tsx
Tool results:
→ result (ok): 1 import type { Option } from '@doctolib/design-system/Option'
2
3 import { MultiSelectDropdownOptions } from './MultiSelectDropdownOptions'
4
5 type MultiSelectDropdownPopupProps = {
6 id: string
7 label: string
8 options: Option[]
9 values: string[]
10 onToggle: (value: string) => void
11 }
12
13 export const MultiSelectDropdownPopup = ({ id, label, options, values, onToggle }: MultiSelectDropdownPopupProps) => (
14 <fieldset
15 id={id}
16 role='dialog'
17 aria-label
…(truncated, 412 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/MultiSelectDropdownLabel.tsx
Tool results:
→ result (ok): 1 type MultiSelectDropdownLabelProps = {
2 htmlFor: string
3 label: string
4 }
5
6 export const MultiSelectDropdownLabel = ({ htmlFor, label }: MultiSelectDropdownLabelProps) => (
7 <label htmlFor={htmlFor} className='oxygen-input-field__label mb-1 block'>
8 {label}
9 </label>
10 )
11
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/MultiSelectDropdownOptions.tsx
Tool results:
→ result (ok): 1 import { Checkbox } from '@doctolib/design-system/Checkbox/v2'
2 import type { Option } from '@doctolib/design-system/Option'
3
4 type MultiSelectDropdownOptionsProps = {
5 options: Option[]
6 values: string[]
7 onToggle: (value: string) => void
8 }
9
10 export const MultiSelectDropdownOptions = ({ options, values, onToggle }: MultiSelectDropdownOptionsProps) => (
11 <>
12 {options.map((option) => (
13 <Checkbox
14 key={option.value}
15 name={option.value}
1
…(truncated, 269 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.tsx
Tool results:
→ result (ok): 1 import { MultiSelectDropdown } from '#components/MultiSelectDropdown'
2 import type { ComponentProps } from 'react'
3
4 export type NhrDocumentCategoryFilterProps = ComponentProps<typeof MultiSelectDropdown>
5
6 export const NhrDocumentCategoryFilter = (props: NhrDocumentCategoryFilterProps) => <MultiSelectDropdown {...props} />
7
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/index.ts
Tool results:
→ result (ok): 1 export * from './NhrDocumentCategoryFilter' 2
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDocumentCategoryFilterConfig.tsx
Tool results:
→ result (ok): 1 import { NhrDocumentCategoryFilter } from '#components/NhrDocumentList/NhrDocumentCategoryFilter'
2 import type { Option } from '@doctolib/design-system/Option'
3
4 import type { FilterConfig } from '../../../types'
5
6 export const createNhrDocumentCategoryFilterConfig = ({
7 options = [],
8 label = 'Document Category',
9 placeholder,
10 }: {
11 options?: Option[]
12 label?: string
13 placeholder?: string
14 } = {}): FilterConfig => ({
15 key: 'typeCodes',
16 render: (filter
…(truncated, 651 more chars)
Tools used:
Bash:ls /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/
Tool results:
→ result (ok): tests business components dev entry.ts i18n.test.ts i18n.ts index.ts locales test-helpers types.ts utils
Tools used:
Bash:ls /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/business/factories/filters/
Tool results:
→ result (ok): createNhrDateSelectionFilterConfig.test.tsx createNhrDateSelectionFilterConfig.tsx createNhrDocumentCategoryFilterConfig.test.tsx createNhrDocumentCategoryFilterConfig.tsx createNhrDocumentStatusFilterConfig.test.tsx createNhrDocumentStatusFilterConfig.tsx createNhrDocumentVisibilityFilterConfig.test.tsx createNhrDocumentVisibilityFilterConfig.tsx
Tools used:
Bash:ls /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/hooks/ 2>/dev/null || echo "no hooks dir"
Tool results:
→ result (ok): no hooks dir
Tools used:
Bash:ls /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/
Tool results:
→ result (ok): MultiSelectDropdown NhrDateRangeFilter NhrDateSelectionFilter NhrDateTypeFilter NhrDocumentList NhrDocumentListFilterPanel NhrDocumentListModal NhrDocumentVisibilityFilter NhrEntryPoint NhrEntryPointButton
Tools used:
Bash:ls /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDateSelectionFilter/ 2>/dev/null
Tool results:
→ result (ok): NhrDateSelectionFilter.stories.tsx NhrDateSelectionFilter.test.tsx NhrDateSelectionFilter.tsx
Tools used:
Bash:ls /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentVisibilityFilter/
Tool results:
→ result (ok): NhrDocumentVisibilityFilter.stories.tsx NhrDocumentVisibilityFilter.test.tsx NhrDocumentVisibilityFilter.tsx
Tools used:
Bash:ls /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/
Tool results:
→ result (ok): index.ts NhrDocumentCategoryFilter.stories.tsx NhrDocumentCategoryFilter.test.tsx NhrDocumentCategoryFilter.tsx
Tools used:
Bash:ls /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/
Tool results:
→ result (ok): NhrDocumentCategoryCell NhrDocumentCategoryFilter NhrDocumentList.stories.tsx NhrDocumentList.test.tsx NhrDocumentList.tsx NhrDocumentNameCell NhrDocumentStatusFilter NhrProcedureDateCell NhrShareDateCell NhrSharedByCell NhrStatusCell NhrVisibilityCell
Tools used:
Bash:cat /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentStatusFilter/index.ts 2>/dev/null && ls /Users/USER/worksp …(truncated, 123 more chars)
Tool results:
→ result (ERROR): Exit code 1
Tools used:
Bash:cat /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/index.ts | head -60
Tool results:
→ result (ERROR): <tool_use_error>Cancelled: parallel tool call Bash(cat /Users/USER/workspace/doctolib/…) errored</tool_use_error>
Tools used:
Bash:ls /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentStatusFilter/
Tool results:
→ result (ok): NhrDocumentStatusFilter.stories.tsx NhrDocumentStatusFilter.test.tsx NhrDocumentStatusFilter.tsx
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/index.ts(1–60)
Tool results:
→ result (ok): 1 export { NhrDocumentList } from './components/NhrDocumentList/NhrDocumentList' 2 export { NhrDocumentListFilterPanel } from './components/NhrDocumentListFilterPanel/NhrDocumentListFilterPanel' 3 export type { 4 ActionConfig, 5 DropdownActionOption, 6 BasicActionConfig, 7 DropdownActionConfig, 8 SimpleActionConfig, 9 ActionsFactory, 10 ColumnConfig, 11 FilterConfig, 12 FilterRenderContext, 13 NhrPatientInfo, 14 NhrVisibilityEntry, 15 StatusOption, 16 NhrDocumentStatusF …(truncated, 2177 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/index.ts(60–?)
Tool results:
→ result (ok): 60 type NhrDocumentVisibilityFilterProps, 61 } from './components/NhrDocumentVisibilityFilter/NhrDocumentVisibilityFilter' 62
Tools used:
Bash:grep -r "MultiSelectDropdown\|NhrDocumentCategoryFilter" /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/index.ts
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/index.ts:export { createNhrDocumentCategoryFilterConfig } from './business/factories/filters/createNhrDocumentCategoryFilterConfig'
Tools used:
Bash:git -C /Users/USER/workspace/doctolib/treatment-plan diff origin/main...HEAD -- packages/tpf-national-health-records-ui/src/index.ts
Tool results:
→ result (ok): (Bash completed with no output)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDocumentCategoryFilterConfig.test.tsx
Tool results:
→ result (ok): 1 import { setup } from '@doctolib/test-helpers'
2 import { screen } from '@testing-library/react'
3 import { afterEach, describe, expect, it, vi } from 'vitest'
4
5 import { mockPatientInfo } from '../../../test-helpers'
6 import { createNhrDocumentCategoryFilterConfig } from './createNhrDocumentCategoryFilterConfig'
7
8 const ctx = { patient: mockPatientInfo }
9
10 const sampleOptions = [
11 { value: 'A', label: 'Report A' },
12 { value: 'B', label: 'Report B' },
13 { value: 'C', labe
…(truncated, 4905 more chars)
Now let me look at the remaining files and the package.json to check export configuration and compare with existing patterns.
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/package.json
Tool results:
→ result (ok): 1 {
2 "name": "@doctolib/tpf-national-health-records-ui",
3 "version": "16.0.2",
4 "repository": {
5 "type": "git",
6 "url": "git+https://github.com/doctolib/treatment-plan.git"
7 },
8 "keywords": [
9 "upfront",
10 "modular frontend",
11 "smaller-scope"
12 ],
13 "author": "@doctolib/fire",
14 "license": "Doctolib",
15 "doctolib": {
16 "upfront-type": "smaller-scope"
17 },
18 "type": "module",
19 "sideEffects": [
20 "./src/i18n.ts"
21 ],
22
…(truncated, 3911 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/MultiSelectDropdown.test.tsx
Tool results:
→ result (ok): 1 import { setup } from '@doctolib/test-helpers'
2 import { screen } from '@testing-library/react'
3 import type { ComponentProps } from 'react'
4 import { afterEach, describe, expect, it, vi } from 'vitest'
5
6 import { MultiSelectDropdown } from './MultiSelectDropdown'
7
8 const sampleOptions = [
9 { value: 'A', label: 'Report A' },
10 { value: 'B', label: 'Report B' },
11 { value: 'C', label: 'Report C' },
12 ]
13
14 describe('MultiSelectDropdown', () => {
15 afterEach(() => {
16
…(truncated, 16079 more chars)
Now I have a complete picture of the PR. Let me check one more thing — the path aliases configuration and whether #business/ and #hooks/ aliases exist.
Tools used:
Bash:grep -r '"#' /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/package.json
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/package.json: "#i18n": "./src/i18n.ts", /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/package.json: "#components/": "./src/components/", /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/package.json: "#data/": "./src/data/.ts", /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/package.js …(truncated, 37 more chars)
Tools used:
Bash:grep -r "NhrDocumentCategoryFilter" /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/index.ts
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/index.ts:export { createNhrDocumentCategoryFilterConfig } from './business/factories/filters/createNhrDocumentCategoryFilterConfig'
Tools used:
Bash:git -C /Users/USER/workspace/doctolib/treatment-plan diff origin/main...HEAD -- packages/tpf-national-health-records-ui/package.json
Tool results:
→ result (ok): diff --git a/packages/tpf-national-health-records-ui/package.json b/packages/tpf-national-health-records-ui/package.json index e75bfbe03..75bbe7d7f 100644 --- a/packages/tpf-national-health-records-ui/package.json +++ b/packages/tpf-national-health-records-ui/package.json @@ -55,6 +55,7 @@ ], "imports": { "#i18n": "./src/i18n.ts", + "#components/": "./src/components/", "#data/": "./src/data/.ts", "#utils/": "./src/utils/.ts" },
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.stories.tsx
Tool results:
→ result (ok): 1 import type { Meta, StoryObj } from '@storybook/react'
2
3 import { NhrDocumentCategoryFilter } from './NhrDocumentCategoryFilter'
4
5 const meta = {
6 title: 'NHR/DocumentList/Filters/DocumentCategoryFilter',
7 component: NhrDocumentCategoryFilter,
8 tags: ['autodocs'],
9 decorators: [
10 (Story) => (
11 <div data-theme='doctolib2023' style={{ height: '40vh', padding: '24px', maxWidth: '400px' }}>
12 <Story />
13 </div>
14 ),
15 ],
16 args: {
17 l
…(truncated, 919 more chars)
Now let me check the stories file for MultiSelectDropdown and look at whether NhrDocumentCategoryFilter has its own index.ts correctly:
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/MultiSelectDropdown.stories.tsx
Tool results:
→ result (ok): 1 import type { Meta, StoryObj } from '@storybook/react'
2 import { useState } from 'react'
3
4 import { MultiSelectDropdown } from './MultiSelectDropdown'
5
6 const meta = {
7 title: 'Components/MultiSelectDropdown',
8 component: MultiSelectDropdown,
9 tags: ['autodocs'],
10 decorators: [
11 (Story) => (
12 <div data-theme='doctolib2023' style={{ height: '40vh', padding: '24px', maxWidth: '400px' }}>
13 <Story />
14 </div>
15 ),
16 ],
17 args: {
18
…(truncated, 1790 more chars)
Tools used:
Bash:grep -rn "default export\|export default" /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/ /Users/USER/workspace/doct …(truncated, 281 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/MultiSelectDropdown.stories.tsx:26:export default meta /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.stories.tsx:23:export default meta
I now have a complete picture of all the changes. Here is the structured architecture review.
Architecture Review: fhir-2219_implement-multi-select-category-filter
Critical Issues (must fix)
1. hooks.ts and helpers.ts use generic names instead of the component-scoped naming convention
Files: /packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/hooks.ts and helpers.ts
The UpFront component directory pattern mandates that local hook and helper files carry the component name as their mantissa:
MultiSelectDropdown.hooks.ts # not hooks.ts
MultiSelectDropdown.helpers.ts # not helpers.ts
The bare names hooks.ts and helpers.ts are reserved for multi-export module-level files in src/hooks/ or src/helpers/. Using generic names inside a component directory violates the naming convention, makes file origin ambiguous in IDE tabs and search results, and undermines the Josh Comeau structure the pattern relies on.
The test files follow the same problem:
- helpers.test.ts should be MultiSelectDropdown.helpers.test.ts
- hooks.test.tsx should be MultiSelectDropdown.hooks.test.tsx
Required renames:
- hooks.ts → MultiSelectDropdown.hooks.ts
- hooks.test.tsx → MultiSelectDropdown.hooks.test.tsx
- helpers.ts → MultiSelectDropdown.helpers.ts
- helpers.test.ts → MultiSelectDropdown.helpers.test.ts
2. NhrDocumentCategoryFilter is placed inside NhrDocumentList/ instead of at the top-level src/components/
Files: /packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/
The existing filters in this package — NhrDateSelectionFilter, NhrDocumentVisibilityFilter, NhrDateRangeFilter, NhrDateTypeFilter — are all top-level siblings under src/components/. Nesting NhrDocumentCategoryFilter inside NhrDocumentList/ breaks that consistency without justification. The filter is not a private sub-component of NhrDocumentList; it is consumed independently via createNhrDocumentCategoryFilterConfig, which imports it directly through the #components/ alias, confirming it is a standalone reusable component.
The correct location is:
src/components/NhrDocumentCategoryFilter/
NhrDocumentCategoryFilter.tsx
NhrDocumentCategoryFilter.test.tsx
NhrDocumentCategoryFilter.stories.tsx
index.ts
Compare with every other filter: /src/components/NhrDocumentVisibilityFilter/NhrDocumentVisibilityFilter.tsx.
3. NhrDocumentCategoryFilter has no index.ts re-exporter
The NhrDocumentCategoryFilter directory currently does have an index.ts (export * from './NhrDocumentCategoryFilter'), but this only exists because it is nested inside NhrDocumentList. The peer filters NhrDateSelectionFilter and NhrDocumentVisibilityFilter have no index.ts — their src/components/ placement makes it unnecessary. If NhrDocumentCategoryFilter moves to the top level (as required by issue 2), it should follow the same pattern as those peers: no index.ts, and any consuming code imports from the file directly or through the #components/ alias pointing at the directory.
The current setup is inconsistent: NhrDocumentCategoryFilter has an index.ts wrapper; NhrDateSelectionFilter and NhrDocumentVisibilityFilter do not.
4. createNhrDocumentCategoryFilterConfig.tsx contains React JSX but lives in src/business/
File: /packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDocumentCategoryFilterConfig.tsx
The src/business/ layer is defined as pure functions only, no React imports. This factory directly renders <NhrDocumentCategoryFilter ... /> inline in the render callback and uses JSX. Importing a React component and returning JSX from a function in src/business/ is a business/presentation layer separation violation.
Look at the pre-existing factories for comparison:
createNhrDateSelectionFilterConfig.tsx and createNhrDocumentVisibilityFilterConfig.tsx both follow the same pattern (they also render JSX from src/business/). This means the violation is pre-existing and not introduced by this PR alone. However, this PR adds a new instance of the same violation and should not perpetuate it.
The FilterConfig.render callback is architecture-imposed — FilterConfig is typed to accept a React render function. If src/business/ must stay as the location for these factories (because that is where the FilterConfig type is materialized), then either the src/business/ rule needs an explicit documented exception for "render factories", or these factories belong in src/components/ alongside the UI components they configure. The PR should at minimum not add a new violating file without acknowledging this tension.
Warnings (should fix)
5. MultiSelectDropdown/index.ts re-exports nothing beyond the main component — internal sub-components are not re-exported but also not hidden
File: /packages/tpf-national-health-records-ui/src/components/MultiSelectDropdown/index.ts
The index.ts only exports MultiSelectDropdown. The sub-components MultiSelectDropdownTrigger, MultiSelectDropdownPopup, MultiSelectDropdownLabel, and MultiSelectDropdownOptions are not in index.ts but are exported as named exports from their own files, making them directly importable by any code in the package. If these are intended as private implementation details of MultiSelectDropdown, they should either:
- Not export their symbols at all (or export only within the component directory), or
- Be explicitly re-exported if they are intentionally part of the public surface.
The current setup is ambiguous. The UpFront rule is that the index.ts defines the component's public surface — anything not in index.ts should not carry a named export if it is internal.
6. #components/* alias was missing before this PR and only added now
File: /packages/tpf-national-health-records-ui/package.json
The imports field gains "#components/*": "./src/components/*" in this PR. This is the right direction, but it means all pre-existing cross-component imports that used relative paths could not use the alias. The PR does use the alias in two new files (NhrDocumentCategoryFilter.tsx and createNhrDocumentCategoryFilterConfig.tsx), which is correct. However, the fact that #business/ and #hooks/ aliases are still absent from the imports field means the architecture cannot be consistently enforced: createNhrDocumentCategoryFilterConfig.tsx uses a relative path '../../../types' (3 levels deep) rather than a # alias for that import. This is acceptable for types.ts which lives outside the standard alias directories, but the incomplete alias set is a broader gap.
7. NhrDocumentCategoryFilter.tsx is a pure pass-through wrapper that adds no value
File: /packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.tsx
export const NhrDocumentCategoryFilter = (props: NhrDocumentCategoryFilterProps) => <MultiSelectDropdown {...props} />
This component does nothing except forward all props. NhrDocumentCategoryFilterProps is defined as ComponentProps<typeof MultiSelectDropdown>, so it is a structural alias, not an abstraction. If MultiSelectDropdown is the implementation and NhrDocumentCategoryFilter is the domain-named public face, the wrapper pattern is legitimate — but it implies MultiSelectDropdown should not appear in src/index.ts public exports, and NhrDocumentCategoryFilter should be what consumers reference. Currently MultiSelectDropdown is not in src/index.ts, so the intent seems correct. However, the wrapper provides zero encapsulation: if MultiSelectDropdown's props change, NhrDocumentCategoryFilterProps changes automatically via ComponentProps<...>, meaning no stable API contract is actually maintained by the wrapper.
…(truncated, 4146 more chars)