{
  "id": "treatment-plan/review-the-pr-https-github-com-doctolib-shiny-axolotl-subagent-aa67e701",
  "slug": "review-the-pr-https-github-com-doctolib-shiny-axolotl-subagent-aa67e701",
  "title": "Session: review-the-pr-https-github-com-doctolib-shiny-axolotl-subagent-aa67e701 — 2026-04-23",
  "type": "source",
  "project": "treatment-plan",
  "date": "2026-04-23",
  "started": "2026-04-23T14:19:19.321000+00:00",
  "ended": "2026-04-23T14:20:39.621000+00:00",
  "model": "claude-sonnet-4-6",
  "cwd": "/Users/USER/workspace/doctolib/treatment-plan",
  "git_branch": "main",
  "permission_mode": "",
  "user_messages": 1,
  "tool_calls": 13,
  "tools_used": [
    "Bash",
    "Read"
  ],
  "is_subagent": true,
  "wikilinks_out": [],
  "body_text": "Project: treatment-plan · Branch: main · Mode:  · Model: claude-sonnet-4-6\n\nStats: 1 user messages, 13 tool calls, tools used: Bash, Read.\n\nConversation\n\nTurn 1 — User\n\nReview PR #3019 in the doctolib/treatment-plan repository for UpFront architecture compliance and React/TypeScript patterns. Do NOT post any comments on the PR — report your findings as text output only.\n\nPR context\n- Title: feat(FHIR-2194): pass document to dropdown action callbacks and add isAvailable filter\n- Package: @doctolib/tpf-national-health-records-ui (located at packages/tpf-national-health-records-ui/)\n- Change type: Frontend feature\n\nWhat changed\n- types.ts: DropdownActionOption.onClick changed from () => void to (doc: NhrDocument) => void | Promise<void>; added isAvailable?: (doc: NhrDocument) => boolean; added 'link-primary' to uiStyle union\n- ActionButton.tsx: filters options by isAvailable, passes document to MoreActions\n- MoreActions.tsx: receives document, injects into onClick at click time\n- NhrDocumentListModal.tsx: threads document through loading-state wrapper\n\nHow to get the diff\ngh pr diff 3019 --repo doctolib/treatment-plan\n\nThe local repo is at /Users/USER/workspace/doctolib/treatment-plan — you can read files directly.\n\nWhat to focus on\n- Separation of concerns: are UI components mixing business logic?\n- Component API design: is the isAvailable predicate pattern the right abstraction?\n- Type design: is NhrDocument being passed at the right layer?\n- UpFront package conventions (naming, exports, barrel files)\n- Any anti-patterns specific to this codebase\n\nReport findings categorized by severity (blocking / suggestion / nit).\n\nTurn 1 — Assistant\n\nI'll start by fetching the PR diff and reading the relevant source files.\n\nTools used:\n\n- Bash: gh pr diff 3019 --repo doctolib/treatment-plan 2>/dev/null || echo \"FAILED\"\n\nTools used:\n\n- Bash: find /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src -type f | sort\n\nTool results:\n\n  → result (ok): diff --git a/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentList.stories.tsx b/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentList.stories.tsx\nindex ee5f3198b1..d4e7919d7d 100644\n--- a/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentList.stories.tsx\n+++ b/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentList.stories.tsx\n@@ -157,7 +157,9 @@ const sharedBySampleR\n…(truncated, 26519 more chars)\n\nTool results:\n\n  → result (ok): /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/__tests__/index.test.ts\n/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/business/factories/actions/createMoreActionsAction.test.ts\n/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/business/factories/actions/createMoreActionsAction.ts\n/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/bus\n…(truncated, 19810 more chars)\n\nNow let me read the key files to understand the full picture.\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/types.ts\n\nTool results:\n\n  → result (ok): 1\timport type { DesignSystemIconComponent } from '@doctolib/design-system-icons'\n2\timport type { NhrDocument } from '@doctolib/tpf-national-health-records'\n3\timport type { ReactNode } from 'react'\n4\t\n5\texport type NhrPatientInfo = {\n6\t  fullName: string\n7\t  gender: 'male' | 'female' | 'other'\n8\t  birthDate: string\n9\t  age: number\n10\t}\n11\t\n12\texport type ColumnConfig = {\n13\t  key: string\n14\t  header: string\n15\t  sortable: boolean\n16\t  / Only required when sortable: true. Returns a string for lo\n…(truncated, 3393 more chars)\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentListModal/NhrDocumentListTable/NhrDocumentListTableBody/ActionButton.tsx\n\nTool results:\n\n  → result (ok): 1\timport type { NhrDocument } from '@doctolib/tpf-national-health-records'\n2\t\n3\timport type { ActionConfig } from '../../../../types'\n4\timport { MoreActions } from './MoreActions'\n5\timport { SimpleAction } from './SimpleAction'\n6\t\n7\ttype ActionButtonProps = {\n8\t  action: ActionConfig\n9\t  row: NhrDocument\n10\t}\n11\t\n12\texport const ActionButton = ({ action, row }: ActionButtonProps) => {\n13\t  if (action.kind === 'dropdown') {\n14\t    if (action.options.length === 0) return null\n15\t\n16\t    return <Mo\n…(truncated, 102 more chars)\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentListModal/NhrDocumentListTable/NhrDocumentListTableBody/MoreActions.tsx\n\nTool results:\n\n  → result (ok): 1\timport { Dropdown } from '@doctolib/design-system/Dropdown'\n2\timport { DropdownTrigger } from '@doctolib/design-system/DropdownTrigger'\n3\timport type { IconName } from '@doctolib/design-system/Icon'\n4\timport { IconButton } from '@doctolib/design-system/IconButton/v2'\n5\t\n6\timport type { DropdownActionConfig, DropdownActionOption } from '../../../../types'\n7\t\n8\ttype DropdownOptionProps = {\n9\t  option: DropdownActionOption\n10\t}\n11\t\n12\tconst DropdownOption = ({ option }: DropdownOptionProps) => {\n\n…(truncated, 1356 more chars)\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentListModal/NhrDocumentListModal.tsx\n\nTool results:\n\n  → result (ok): 1\timport { Modal } from '@doctolib/design-system/Modal'\n2\timport { ModalBody } from '@doctolib/design-system/ModalBody'\n3\timport type { NhrDocument } from '@doctolib/tpf-national-health-records'\n4\timport { useEffect, useMemo, useState } from 'react'\n5\t\n6\timport type { ActionConfig, ColumnConfig, FilterConfig, NhrPatientInfo, SearchDocumentsFn } from '../../types'\n7\timport { NhrDocumentList } from '../NhrDocumentList/NhrDocumentList'\n8\t\n9\texport type NhrDocumentListModalProps = {\n10\t  isOpen: boo\n…(truncated, 2809 more chars)\n\nI can see the diff references the new version of the files but the local repo is on main (clean state) — so I'm reading the pre-PR state. Let me check the current state of the files that were modified to understand what's already there vs. what the PR is adding.\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentListModal/NhrDocumentListTable/NhrDocumentListTableBody/NhrDocumentListTableBody.tsx\n\nTool results:\n\n  → result (ok): 1\timport { TableBody } from '@doctolib/design-system/TableBody'\n2\timport { TableCell } from '@doctolib/design-system/TableCell'\n3\timport { TableRow } from '@doctolib/design-system/TableRow'\n4\timport { TableSelectCell } from '@doctolib/design-system/TableSelectCell'\n5\timport type { NhrDocument } from '@doctolib/tpf-national-health-records'\n6\t\n7\timport type { ActionConfig, ColumnConfig } from '../../../../types'\n8\timport { ActionButton } from './ActionButton'\n9\t\n10\texport type NhrDocumentListTable\n…(truncated, 1105 more chars)\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/business/factories/actions/createMoreActionsAction.ts\n\nTool results:\n\n  → result (ok): 1\timport { EllipsisVerticalRegularIcon } from '@doctolib/design-system-icons/EllipsisVerticalRegularIcon'\n2\t\n3\timport type { DropdownActionConfig, DropdownActionOption } from '../../../types'\n4\t\n5\texport const createMoreActionsAction = (options: DropdownActionOption[]): DropdownActionConfig => ({\n6\t  kind: 'dropdown',\n7\t  key: 'more',\n8\t  tooltip: 'More actions',\n9\t  iconComponent: EllipsisVerticalRegularIcon,\n10\t  variant: 'transparent',\n11\t  options,\n12\t})\n13\n\nTools used:\n\n- Bash: find /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui -name \"package.json\" | head -3 | xargs -I{} cat {}\n\nTool results:\n\n  → result (ok): {\n  \"name\": \"@doctolib/tpf-national-health-records-ui\",\n  \"version\": \"14.1.0\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/doctolib/treatment-plan.git\"\n  },\n  \"keywords\": [\n    \"upfront\",\n    \"modular frontend\",\n    \"smaller-scope\"\n  ],\n  \"author\": \"@doctolib/fire\",\n  \"license\": \"Doctolib\",\n  \"doctolib\": {\n    \"upfront-type\": \"smaller-scope\"\n  },\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"./src/i18n.ts\"\n  ],\n  \"scripts\": {\n    \"dev\": \"vite --host -d\",\n    \"dev:mfr\": \n…(truncated, 8746 more chars)\n\nThe local repo is on main (pre-PR state). I have the full diff and the pre-change source files. Let me now read the src/__tests__ directory to check whether tests are properly colocated, and also look at the utils/ directory to understand the structure.\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/__tests__/index.test.ts\n\nTool results:\n\n  → result (ok): 1\timport { describe, expect, it } from 'vitest'\n2\t\n3\tdescribe('tpf-national-health-records-ui', () => {\n4\t  it('should have basic test coverage', () => {\n5\t    expect(true).toBe(true)\n6\t  })\n7\t})\n8\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/index.ts\n\nTool results:\n\n  → result (ok): 1\texport { NhrDocumentList } from './components/NhrDocumentList/NhrDocumentList'\n2\texport { NhrDocumentListFilterPanel } from './components/NhrDocumentListFilterPanel/NhrDocumentListFilterPanel'\n3\texport type {\n4\t  ActionConfig,\n5\t  DropdownActionOption,\n6\t  BasicActionConfig,\n7\t  DropdownActionConfig,\n8\t  SimpleActionConfig,\n9\t  ActionsFactory,\n10\t  ColumnConfig,\n11\t  FilterConfig,\n12\t  FilterRenderContext,\n13\t  NhrPatientInfo,\n14\t  NhrVisibilityEntry,\n15\t  StatusOption,\n16\t  NhrDocumentStatusF\n…(truncated, 2233 more chars)\n\nNow I have a thorough picture. Let me check one more thing — whether isAvailable is exported from src/index.ts (since DropdownActionOption is exported) and whether there's a shouldBeVisible naming inconsistency in tests.\n\nTools used:\n\n- Bash: grep -n \"isAvailable\\|shouldBeVisible\" /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentListModal/NhrDocumentListTable/NhrDocumentLi\n…(truncated, 56 more chars)\n\nTool results:\n\n  → result (ok): (Bash completed with no output)\n\nTools used:\n\n- Bash: grep -rn \"isAvailable\\|shouldBeVisible\\|link-primary\" /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/ 2>/dev/null | grep -v \".test.\" | grep -v \"stories\"\n\nTool results:\n\n  → result (ok): (Bash completed with no output)\n\nThe local files are still in the pre-PR state (the diff hasn't been applied). I have all the information I need from the diff. Let me now produce the review.\n\n---\n\nArchitecture Review: PR #3019\n\nPackage: @doctolib/tpf-national-health-records-ui\nScope of change: src/types.ts, MoreActions.tsx, ActionButton.tsx, NhrDocumentListModal.tsx, associated tests and stories.\n\n---\n\nCritical Issues (must fix)\n\n1. NhrDocumentListModal.tsx wraps onClick with a loading guard but still hardcodes async (doc: NhrDocument) =>\n\nFile: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentListModal/NhrDocumentListModal.tsx (line 60)\n\nThe wrappedActions memo transforms each dropdown option by replacing its onClick with a new arrow function. In the _new_ signature the wrapper correctly receives doc and threads it through. However, the inline annotation async (doc: NhrDocument) => introduces a direct import of NhrDocument into NhrDocumentListModal.tsx. This type is already leaked into MoreActions.tsx and ActionButton.tsx as part of this PR. That is architecturally sound for _internal_ presentational components, but NhrDocumentListModal is the public-facing container. Typing the wrapper argument explicitly here couples the container to the domain type in a way that could be avoided:\n\n// current (introduces explicit type annotation inside wrappedActions)\nonClick: async (doc: NhrDocument) => {\n\n// preferred — let TypeScript infer from the option.onClick signature\nonClick: async (doc) => {\n\nThis is a type-annotation hygiene issue. The NhrDocument import already exists in NhrDocumentListModal.tsx (used for the rows state), so there is no new import required — but removing the explicit annotation is cleaner. Not a blocker on its own, but worth noting.\n\nSeverity: Warning (see below — combined with issue 2 this becomes a design concern).\n\n---\n\n2. isAvailable filtering is done in ActionButton (rendering layer), not in the business layer\n\nFile: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentListModal/NhrDocumentListTable/NhrDocumentListTableBody/ActionButton.tsx\n\nconst visibleOptions = action.options.filter((opt) => !opt.isAvailable || opt.isAvailable(row))\n\nThe UpFront rule is that src/business/ handles pure functions and domain logic, while components are presentational. The isAvailable predicate is a domain rule (\"should this action be shown for this document?\") being evaluated inline inside a component. This is a mild violation of the business/presentation boundary.\n\nThe correct home for this filtering is a helper in src/business/ or at minimum a .helpers.ts file colocated with ActionButton. The existing createMoreActionsAction factory in src/business/factories/actions/ is the natural extension point: callers could pass isAvailable predicates and the factory (or a thin filterActionOptions(action, doc) helper) would return a filtered DropdownActionConfig. The component would then receive an already-filtered config and remain purely structural.\n\nThis is not a blocker if the team has consciously decided to keep filtering in the component for simplicity, but it should be called out explicitly as a deviation from the pattern established by src/business/.\n\nSeverity: Warning\n\n---\n\n3. ActionButton.tsx (pre-PR state) does not pass row to MoreActions — the PR fixes this but introduces a shape mismatch\n\nFile: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentListModal/NhrDocumentListTable/NhrDocumentListTableBody/ActionButton.tsx\n\nThe pre-PR ActionButton reads:\n\nreturn <MoreActions action={action} />\n\nIt never passed row to MoreActions, which is correct in the old design where onClick: () => void. After this PR, ActionButton passes doc={row} and also reconstructs a new DropdownActionConfig object via spread:\n\nreturn <MoreActions action={{ ...action, options: visibleOptions }} doc={row} />\n\nThe spread creates a new object on every render. For a pure component like MoreActions this causes unnecessary re-renders if MoreActions ever receives React.memo. This is a minor performance nit but worth noting. The correct pattern is:\n\n// pre-filter happens once, stable reference via useMemo or passed directly\nconst visibleOptions = useMemo(\n  () => action.options.filter((opt) => !opt.isAvailable || opt.isAvailable(row)),\n  [action.options, row]\n)\nif (visibleOptions.length === 0) return null\nreturn <MoreActions action={action} visibleOptions={visibleOptions} doc={row} />\n\nOr simply accept the spread as-is if MoreActions is never memoized (currently it is not). Flag as a nit rather than a blocker.\n\n---\n\nWarnings (should fix)\n\n4. Naming inconsistency: isAvailable in production code vs. shouldBeVisible in tests\n\nFile: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentListModal/NhrDocumentListTable/NhrDocumentListTableBody/ActionButton.test.tsx\n\nTwo tests use the variable name shouldBeVisible for the mock:\n\nit('should render the option when shouldBeVisible returns true for the row', () => {\n  ...\n  isAvailable: vi.fn().mockReturnValue(true),\n\nit('should pass the row to shouldBeVisible', () => {\n  const shouldBeVisible = vi.fn().mockReturnValue(true)\n  ...\n  isAvailable: shouldBeVisible,\n\nThe test descriptions and local variable names say shouldBeVisible; the property in the type and the production code says isAvailable. These are inconsistent. If the team considered shouldBeVisible as an alternative name during design and chose isAvailable, the tests should be updated to match. If shouldBeVisible was the intended name, types.ts should be updated. As shipped, this leaves the next developer uncertain about intent.\n\nFix: align test description text and local variable names to isAvailable.\n\n---\n\n5. DropdownActionOption.uiStyle now accepts 'link-primary' but Dropdown.Button handling does not differentiate it from 'link-danger'\n\nFile: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentListModal/NhrDocumentListTable/NhrDocumentListTableBody/MoreActions.tsx\n\nThe pre-PR code checks if (option.uiStyle) and renders a Dropdown.Button. The post-PR code adds 'link-primary' to the union but the conditional remains if (option.uiStyle) — any truthy value goes through Dropdown.Button. The existing FIXME comment (Dropdown.Button types are incorrect) implies this branch is already a type cast workaround. Adding another valid value to the union without verifying that Dropdown.Button actually accepts 'link-primary' at runtime is a risk. There is a test for it (should call option.onClick with the row when a primary option is clicked) that only asserts the click callback is invoked — it does not assert the button is actually rendered with primary styling.\n\nRecommendation: Add a Storybook story or assertion that visually verifies link-primary renders as intended, and verify the Dropdown.Button design system component accepts this variant.\n\n---\n\n6. src/__tests__/index.test.ts is a tests/ directory anti-pattern\n\nFile: /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/__tests__/index.test.ts\n\nThis is a pre-existing issue, not introduced by this PR, but worth flagging because the PR adds tests and the overall package structure is under review. Per the UpFront rule, test files must be colocated with source. The src/__tests__/ directory is not a valid location for test files — it should be used only for test support (setup, utilities). The index.test.ts file there is a trivial placeholder (expect(true).toBe(true)) and sh\n…(truncated, 2507 more chars)",
  "sha256": "30ba4ada247916c2",
  "source_url": "sessions/treatment-plan/2026-04-23T14-19-treatment-plan-review-the-pr-https-github-com-doctolib-shiny-axolotl-subagent-aa67e701.html"
}