Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
✅ Deploy Preview for ohif-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReact 19 support is introduced across build tooling, package metadata, application routing, core hooks, and UI components. React Compiler lint budgeting is added to CI, runtime PropTypes and selected memoization patterns are removed, and rsbuild becomes the primary application build path. ChangesReact Compiler and build tooling
Application and component migration
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Viewers
|
||||||||||||||||||||||||||||
| Project |
Viewers
|
| Branch Review |
ohifReact
|
| Run status |
|
| Run duration | 02m 01s |
| Commit |
|
| Committer | Bill Wallace |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
28
|
| View all changes introduced in this branch ↗︎ | |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
platform/ui-next/src/components/InputMultiSelect/InputMultiSelect.tsx (1)
112-124: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winKeep these callbacks stable in effect dependencies
platform/ui-next/src/components/InputMultiSelect/InputMultiSelect.tsx#L112-L140:measureis recreated on every render, so the layout effect runs again aftersetCoordsschedules a render and can loop while the popup is open.platform/ui-next/src/components/ScrollArea/ScrollArea.tsx#L45-L57:checkScrollshould also be stable so the resize listener isn’t removed and re-added on every render.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@platform/ui-next/src/components/InputMultiSelect/InputMultiSelect.tsx` around lines 112 - 124, Stabilize the measure callback in InputMultiSelect.tsx (InputMultiSelect) with the appropriate callback memoization and dependencies so the layout effect does not rerun after setCoords-driven renders; also stabilize checkScroll in ScrollArea.tsx (ScrollArea) at lines 45-51 so its resize listener is not unnecessarily recreated. Update each effect’s dependencies to use the stable callbacks.
🧹 Nitpick comments (9)
rsbuild.config.ts (1)
112-116: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPreserve the Babel preset’s unrelated defaults.
Replacing the options with
{}also drops defaults such asallowDeclareFields,allowNamespaces, andoptimizeConstEnums; onlyallExtensionsandisTSXneed removal. The official plugin documentation confirms these are part of its default preset configuration. (rsbuild.dev)Proposed fix
- opts.presets = opts.presets?.map(preset => - Array.isArray(preset) && String(preset[0]).includes('preset-typescript') - ? [preset[0], {}] - : preset - ); + opts.presets = opts.presets?.map(preset => { + if (!Array.isArray(preset) || !String(preset[0]).includes('preset-typescript')) { + return preset; + } + + const { allExtensions, isTSX, ...options } = preset[1] ?? {}; + return [preset[0], options, ...preset.slice(2)]; + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rsbuild.config.ts` around lines 112 - 116, Update the preset transformation in the opts.presets mapping to remove only allExtensions and isTSX from the existing preset options while preserving unrelated Babel defaults such as allowDeclareFields, allowNamespaces, and optimizeConstEnums. Keep the preset identifier and non-array presets unchanged.eslint.config.mjs (1)
57-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRestrict the
forwardRefimport, not only its local name.
import { forwardRef as wrapRef } from 'react'bypasses both selectors. Add an import restriction so aliases cannot reintroduce the deprecated pattern.Proposed fix
paths: [ { name: 'prop-types', message: 'propTypes were removed; use TypeScript types.', }, + { + name: 'react', + importNames: ['forwardRef'], + message: 'React 19: accept ref as a regular prop instead of forwardRef.', + }, ],🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@eslint.config.mjs` around lines 57 - 85, Update the no-restricted-imports configuration to restrict importing forwardRef from React regardless of the local alias, while preserving the existing React.forwardRef and call-expression restrictions. Add the import-level restriction alongside the existing prop-types path rule in the no-restricted-imports paths configuration.platform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx (1)
1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace removed PropTypes with TypeScript prop contracts.
These TypeScript components now expose implicitly
anyprops after their runtime schemas were deleted.
platform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx#L1-L1: define themessagesandidcontract.platform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx#L62-L62: retain static validation where the PropTypes schema was removed.platform/ui-next/src/components/Viewport/PatientInfo.tsx#L1-L2: define patient fields, display values, andshowPatientInfoRef.platform/ui-next/src/components/Viewport/PatientInfo.tsx#L124-L124: replace the removed runtime schema with that interface.platform/ui-next/src/components/Viewport/ViewportActionArrows.tsx#L2-L3: typeonArrowsClickandclassName.platform/ui-next/src/components/Viewport/ViewportActionArrows.tsx#L34-L34: replace the removed schema with the typed props contract.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@platform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx` at line 1, Replace the implicit any props with explicit TypeScript contracts: in platform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx lines 1-1 and 62-62, type DisplaySetMessageListTooltip’s messages and id props and retain its static validation; in platform/ui-next/src/components/Viewport/PatientInfo.tsx lines 1-2 and 124-124, define and apply a PatientInfo props interface covering patient fields, display values, and showPatientInfoRef; in platform/ui-next/src/components/Viewport/ViewportActionArrows.tsx lines 2-3 and 34-34, define and apply typed props for onArrowsClick and className.platform/ui-next/src/components/ScrollArea/ScrollArea.tsx (1)
45-51: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winKeep
checkScrollstable while it is an effect dependency.The new function identity causes the resize-listener effect to clean up and re-register after every render, including renders caused by these state setters.
Proposed fix
- const checkScroll = () => { + const checkScroll = React.useCallback(() => { if (viewportRef.current) { const { scrollHeight, clientHeight, scrollTop } = viewportRef.current; setShowBottomArrow(scrollHeight > clientHeight && scrollTop < scrollHeight - clientHeight); setShowTopArrow(scrollTop > 0); } - }; + }, []);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@platform/ui-next/src/components/ScrollArea/ScrollArea.tsx` around lines 45 - 51, Wrap the checkScroll function in a stable useCallback so its identity does not change across renders while it is used as the resize-listener effect dependency. Include the existing viewportRef access and state updates unchanged, with the appropriate dependency list.platform/ui-next/src/components/Separator/Separator.tsx (1)
10-28: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winUse inline styles for dynamic dimensions instead of Tailwind arbitrary class interpolation.
Tailwind's compiler cannot detect and generate CSS for dynamically constructed arbitrary values like
`h-[${thickness}]`at build time. Unless these specific dimensions are safelisted or appear elsewhere in the codebase as static string literals, the styles will not be applied to the DOM.Consider moving the dynamic thickness logic to an inline
styleprop to ensure it reliably renders.♻️ Proposed refactor
const Separator = ({ className, orientation = 'horizontal', decorative = true, thickness = '1px', ref, + style, ...props }: SeparatorProps) => ( <SeparatorPrimitive.Root ref={ref} decorative={decorative} orientation={orientation} className={cn( 'bg-border shrink-0', - orientation === 'horizontal' ? `h-[${thickness}] w-full` : `h-full w-[${thickness}]`, + orientation === 'horizontal' ? 'w-full' : 'h-full', className )} + style={{ + ...(orientation === 'horizontal' ? { height: thickness } : { width: thickness }), + ...style, + }} {...props} /> );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@platform/ui-next/src/components/Separator/Separator.tsx` around lines 10 - 28, Update the Separator component’s dynamic thickness handling in its SeparatorPrimitive.Root className construction: remove the interpolated Tailwind h-[${thickness}]/w-[${thickness}] classes and apply thickness through the inline style prop, while preserving the orientation-specific full-size classes and existing className merging.extensions/cornerstone/src/panels/PanelSegmentation.tsx (1)
98-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider using
useCustomizationfor the other customization keys to ensure reactivity.Currently, only
panelSegmentation.onSegmentationAddutilizes theuseCustomizationhook to protect against stale data when dynamically overridden. Direct calls togetCustomizationfor the surrounding variables will still be heavily memoized by the React Compiler and could fail to update if a mode dynamically replaces them after the panel's initial mount.For consistency and future-proofing against similar bugs, consider reading all customization values via
useCustomization.♻️ Proposed refactor
- const segmentationTableMode = customizationService.getCustomization( - 'panelSegmentation.tableMode' - ) as unknown as string; + const segmentationTableMode = useCustomization<string>('panelSegmentation.tableMode'); // onSegmentationAdd is read through useCustomization (not a direct // getCustomization call) so the panel re-renders when a mode registers its // handler after this panel first mounted - e.g. TMTV replaces it with its // create-labelmap-from-PT command in onModeEnter, and a render-time read // (memoized by the React Compiler) would keep serving the stale default. const onSegmentationAdd = useCustomization('panelSegmentation.onSegmentationAdd'); - const disableEditing = customizationService.getCustomization('panelSegmentation.disableEditing'); - const showAddSegment = customizationService.getCustomization('panelSegmentation.showAddSegment'); - const CustomDropdownMenuContent = customizationService.getCustomization( - 'panelSegmentation.customDropdownMenuContent' - ); - - const CustomSegmentStatisticsHeader = customizationService.getCustomization( - 'panelSegmentation.customSegmentStatisticsHeader' - ); + const disableEditing = useCustomization<boolean>('panelSegmentation.disableEditing'); + const showAddSegment = useCustomization<boolean>('panelSegmentation.showAddSegment'); + const CustomDropdownMenuContent = useCustomization<any>('panelSegmentation.customDropdownMenuContent'); + const CustomSegmentStatisticsHeader = useCustomization<any>('panelSegmentation.customSegmentStatisticsHeader');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@extensions/cornerstone/src/panels/PanelSegmentation.tsx` around lines 98 - 115, Update the customization reads in the PanelSegmentation component so segmentationTableMode, disableEditing, showAddSegment, CustomDropdownMenuContent, and CustomSegmentStatisticsHeader use useCustomization instead of direct customizationService.getCustomization calls. Preserve each existing customization key and leave the already-reactive onSegmentationAdd usage unchanged.platform/core/src/hooks/useCustomization.ts (1)
19-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider using
useSyncExternalStorefor external state subscriptions.React 18+ provides
useSyncExternalStore, which is specifically designed for reading and subscribing to external data sources. It guarantees consistency during concurrent rendering and natively handles returning function values safely (avoiding the need for thesetValue(() => ...)functional updater workaround).Since
getCustomizationreturns a stable reference for unchanged values,useSyncExternalStoreis an ideal and idiomatic fit here.♻️ Proposed refactor
-import { useState, useEffect } from 'react'; +import { useSyncExternalStore, useCallback } from 'react'; import { useSystem } from '../contextProviders/SystemProvider'; /** * Reads a customization and re-renders when customizations change. * * `customizationService.getCustomization` called directly during render * captures whatever is registered at that moment. Registration order is not * guaranteed: mode-scope customizations are registered in `mode.onModeEnter`, * which can run after panels have already rendered, and both the React * Compiler (which memoizes the call on the stable service reference) and * components that snapshot the value would then keep serving the stale * pre-registration value. This hook subscribes to the service's modification * events, so consumers always converge on the currently registered value. - * - * The setState updater form is used everywhere because a customization value - * may itself be a function. */ export function useCustomization<T = unknown>(customizationId: string): T { const { servicesManager } = useSystem(); const { customizationService } = servicesManager.services; - const [value, setValue] = useState<T>( - () => customizationService.getCustomization(customizationId) as T - ); - - useEffect(() => { - const update = () => { - // getCustomization caches the transformed value, so an unchanged - // customization returns the same reference and setState bails out. - setValue(() => customizationService.getCustomization(customizationId) as T); - }; - - // Catch registrations that happened between render and effect. - update(); - - // Mode-scope registrations are the ones that race component mounting - // (they run in mode.onModeEnter); global and default customizations are - // registered before the app renders, so re-reading on the mode event is - // sufficient and keeps the re-render surface small. - const subscription = customizationService.subscribe( - customizationService.EVENTS.MODE_CUSTOMIZATION_MODIFIED, - update - ); - - return () => { - subscription.unsubscribe(); - }; - }, [customizationService, customizationId]); - - return value; + const subscribe = useCallback( + (onStoreChange: () => void) => { + const subscription = customizationService.subscribe( + customizationService.EVENTS.MODE_CUSTOMIZATION_MODIFIED, + onStoreChange + ); + return () => subscription.unsubscribe(); + }, + [customizationService] + ); + + const getSnapshot = useCallback( + () => customizationService.getCustomization(customizationId) as T, + [customizationService, customizationId] + ); + + return useSyncExternalStore(subscribe, getSnapshot); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@platform/core/src/hooks/useCustomization.ts` around lines 19 - 52, Refactor useCustomization to use React’s useSyncExternalStore instead of local useState/useEffect subscription management. Provide a snapshot getter that reads customizationService.getCustomization(customizationId), subscribe to MODE_CUSTOMIZATION_MODIFIED through customizationService, and preserve the existing cleanup and stable-reference behavior.platform/core/src/hooks/useActiveViewportDisplaySets.ts (1)
11-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove stale comments referencing removed manual memoization.
Since
useCallbackanduseMemowrappers were removed in favor of relying on the React Compiler's automatic memoization, these inline comments are now outdated and may confuse future developers.
platform/core/src/hooks/useActiveViewportDisplaySets.ts#L11-L19: remove the// Move this function outside useEffect and memoize itcomment abovegetDisplaySetsForViewport.platform/core/src/hooks/useActiveViewportDisplaySets.ts#L51-L55: remove or update the// Only depend on stable referencescomment next to the dependency array.extensions/cornerstone/src/components/ViewportWindowLevel/ViewportWindowLevel.tsx#L175-L176: reword// Create a memoized version of displaySet IDs for comparisonto something like// Extract and sort displaySet IDs for comparison.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@platform/core/src/hooks/useActiveViewportDisplaySets.ts` around lines 11 - 19, Remove the outdated manual-memoization comment above getDisplaySetsForViewport in platform/core/src/hooks/useActiveViewportDisplaySets.ts (anchor, lines 11-19). Remove or update the “Only depend on stable references” comment near its dependency array in the same file (sibling, lines 51-55). Reword the display-set ID comment in extensions/cornerstone/src/components/ViewportWindowLevel/ViewportWindowLevel.tsx (sibling, lines 175-176) to describe extracting and sorting IDs for comparison rather than memoization.extensions/default/src/DicomTagBrowser/DicomTagTable.tsx (1)
248-262: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoffExtract
RowListand useitemDatato prevent unmounting.Defining
RowListinline as a returned component causes its reference to change whenevergetRowComponentis called. When passed toreact-window's<List>, this forces React to unmount and remount every visible row on every render, destroying DOM nodes and resetting internal hook states (likeuseMemo). Furthermore, the React Compiler strictly forbids defining components with hooks inside other functions.Consider extracting
RowListoutside ofDicomTagTableand usingreact-window'sitemDataprop to passrowsandonToggle.♻️ Proposed refactor
Extract the row component outside:
const RowList = ({ index, style, data }) => { const { rows, onToggle } = data; const row = rows[index]; // No need for useMemo here, it's just a lookup return ( <RowComponent style={style} row={row} keyPrefix={`DICOMTagRow-${index}`} onToggle={onToggle(row)} /> ); };Then in
DicomTagTable:// Inside DicomTagTable const itemData = useMemo(() => ({ rows: visibleRows, onToggle, }), [visibleRows, onToggle]); return ( ... <List ref={listRef} height={500} itemCount={visibleRows.length} itemSize={getItemSize(visibleRows)} width={'100%'} itemData={itemData} className="ohif-scrollbar text-foreground" > {RowList} </List> )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@extensions/default/src/DicomTagBrowser/DicomTagTable.tsx` around lines 248 - 262, Extract the inline RowList component from getRowComponent and define it at module scope, removing its useMemo row lookup. Pass rows and onToggle through a memoized itemData object in DicomTagTable, provide itemData to the react-window List, and render the stable RowList component so visible rows are not unmounted between renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@extensions/cornerstone/src/components/ViewportWindowLevel/ViewportWindowLevel.tsx`:
- Around line 136-140: Update the component’s mount-time initialization around
handleImageVolumeLoadingCompleted to inspect the viewport’s image volumes’
initial load status. Set isLoading to false and perform the existing histogram
update when all relevant volumes are already fully loaded, while preserving the
event-driven handler for volumes that finish loading after mount.
In `@extensions/default/src/DicomTagBrowser/DicomTagTable.tsx`:
- Around line 116-117: Update the listRef and canvasRef declarations to use
explicit nullable ref types: List | null for the List instance and
HTMLCanvasElement | null for the canvas element, initializing both with null
instead of undefined. Preserve their existing usage below.
In `@platform/ui-next/src/components/Card/Card.tsx`:
- Around line 31-42: Update the ref prop type in CardTitle from
React.Ref<HTMLParagraphElement> to React.Ref<HTMLHeadingElement> so it matches
the underlying h3 element while preserving the existing forwarded ref behavior.
In `@platform/ui-next/src/components/OHIFDialogs/InputDialog.tsx`:
- Around line 263-270: In the InputDialog action markup, remove the duplicate
onClick handler from either the wrapper div or FooterAction.Primary so clicking
the primary action invokes onClick(value) only once. Move
data-cy="input-dialog-save-button" onto the remaining actionable
FooterAction.Primary element and preserve the existing ref/props behavior on the
wrapper.
In `@platform/ui-next/src/contextProviders/ManagedDialog.tsx`:
- Around line 78-86: Update the useImperativeHandle call in ManagedDialog so its
dependency array includes contentNode, ensuring updatePosition closes over the
mounted node instead of the initial null value. Preserve the existing
_updatePosition invocation and ref API.
In `@scripts/reactCompilerLintBudget.mjs`:
- Around line 43-46: Update the budget comparison branch in
reactCompilerLintBudget so that when errors or warnings are below their
committed budget, it exits with a nonzero status after logging the tightening
message. Preserve the existing comparison and output, and ensure the
stale-budget path fails the script as required by the documented budget
contract.
---
Outside diff comments:
In `@platform/ui-next/src/components/InputMultiSelect/InputMultiSelect.tsx`:
- Around line 112-124: Stabilize the measure callback in InputMultiSelect.tsx
(InputMultiSelect) with the appropriate callback memoization and dependencies so
the layout effect does not rerun after setCoords-driven renders; also stabilize
checkScroll in ScrollArea.tsx (ScrollArea) at lines 45-51 so its resize listener
is not unnecessarily recreated. Update each effect’s dependencies to use the
stable callbacks.
---
Nitpick comments:
In `@eslint.config.mjs`:
- Around line 57-85: Update the no-restricted-imports configuration to restrict
importing forwardRef from React regardless of the local alias, while preserving
the existing React.forwardRef and call-expression restrictions. Add the
import-level restriction alongside the existing prop-types path rule in the
no-restricted-imports paths configuration.
In `@extensions/cornerstone/src/panels/PanelSegmentation.tsx`:
- Around line 98-115: Update the customization reads in the PanelSegmentation
component so segmentationTableMode, disableEditing, showAddSegment,
CustomDropdownMenuContent, and CustomSegmentStatisticsHeader use
useCustomization instead of direct customizationService.getCustomization calls.
Preserve each existing customization key and leave the already-reactive
onSegmentationAdd usage unchanged.
In `@extensions/default/src/DicomTagBrowser/DicomTagTable.tsx`:
- Around line 248-262: Extract the inline RowList component from getRowComponent
and define it at module scope, removing its useMemo row lookup. Pass rows and
onToggle through a memoized itemData object in DicomTagTable, provide itemData
to the react-window List, and render the stable RowList component so visible
rows are not unmounted between renders.
In `@platform/core/src/hooks/useActiveViewportDisplaySets.ts`:
- Around line 11-19: Remove the outdated manual-memoization comment above
getDisplaySetsForViewport in
platform/core/src/hooks/useActiveViewportDisplaySets.ts (anchor, lines 11-19).
Remove or update the “Only depend on stable references” comment near its
dependency array in the same file (sibling, lines 51-55). Reword the display-set
ID comment in
extensions/cornerstone/src/components/ViewportWindowLevel/ViewportWindowLevel.tsx
(sibling, lines 175-176) to describe extracting and sorting IDs for comparison
rather than memoization.
In `@platform/core/src/hooks/useCustomization.ts`:
- Around line 19-52: Refactor useCustomization to use React’s
useSyncExternalStore instead of local useState/useEffect subscription
management. Provide a snapshot getter that reads
customizationService.getCustomization(customizationId), subscribe to
MODE_CUSTOMIZATION_MODIFIED through customizationService, and preserve the
existing cleanup and stable-reference behavior.
In
`@platform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx`:
- Line 1: Replace the implicit any props with explicit TypeScript contracts: in
platform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx
lines 1-1 and 62-62, type DisplaySetMessageListTooltip’s messages and id props
and retain its static validation; in
platform/ui-next/src/components/Viewport/PatientInfo.tsx lines 1-2 and 124-124,
define and apply a PatientInfo props interface covering patient fields, display
values, and showPatientInfoRef; in
platform/ui-next/src/components/Viewport/ViewportActionArrows.tsx lines 2-3 and
34-34, define and apply typed props for onArrowsClick and className.
In `@platform/ui-next/src/components/ScrollArea/ScrollArea.tsx`:
- Around line 45-51: Wrap the checkScroll function in a stable useCallback so
its identity does not change across renders while it is used as the
resize-listener effect dependency. Include the existing viewportRef access and
state updates unchanged, with the appropriate dependency list.
In `@platform/ui-next/src/components/Separator/Separator.tsx`:
- Around line 10-28: Update the Separator component’s dynamic thickness handling
in its SeparatorPrimitive.Root className construction: remove the interpolated
Tailwind h-[${thickness}]/w-[${thickness}] classes and apply thickness through
the inline style prop, while preserving the orientation-specific full-size
classes and existing className merging.
In `@rsbuild.config.ts`:
- Around line 112-116: Update the preset transformation in the opts.presets
mapping to remove only allExtensions and isTSX from the existing preset options
while preserving unrelated Babel defaults such as allowDeclareFields,
allowNamespaces, and optimizeConstEnums. Keep the preset identifier and
non-array presets unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: fbaf4258-2686-4294-a532-332fb0c2dab2
⛔ Files ignored due to path filters (4)
platform/app/.webpack/InjectServiceWorkerManifestPlugin.jsis excluded by!**/.webpack/**platform/app/.webpack/webpack.pwa.jsis excluded by!**/.webpack/**platform/ui-next/31fb9346313fc3740d7b.woff2is excluded by!**/*.woff2pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (193)
.circleci/config.yml.gitignore.react-compiler-lint-budget.jsonbabel.config.jseslint.config.mjsextensions/cornerstone-dicom-pmap/package.jsonextensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsxextensions/cornerstone-dicom-rt/package.jsonextensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsxextensions/cornerstone-dicom-seg/package.jsonextensions/cornerstone-dicom-sr/package.jsonextensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContainer.tsxextensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContentItem.tsxextensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsxextensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRTextViewport.tsxextensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRViewport.tsxextensions/cornerstone-dynamic-volume/package.jsonextensions/cornerstone/package.jsonextensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsxextensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsxextensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsxextensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsxextensions/cornerstone/src/Viewport/Overlays/ViewportSliceProgressScrollbar/ViewportSliceProgressScrollbar.tsxextensions/cornerstone/src/components/ActiveViewportWindowLevel/ActiveViewportWindowLevel.tsxextensions/cornerstone/src/components/DicomUpload/DicomUpload.tsxextensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsxextensions/cornerstone/src/components/DicomUpload/DicomUploadProgressItem.tsxextensions/cornerstone/src/components/NavigationComponent/NavigationComponent.tsxextensions/cornerstone/src/components/SegmentationUtilityButton.tsxextensions/cornerstone/src/components/SelectItemWithModality.tsxextensions/cornerstone/src/components/ViewportColorbar/ViewportColorbar.tsxextensions/cornerstone/src/components/ViewportColorbar/ViewportColorbarsContainer.tsxextensions/cornerstone/src/components/ViewportWindowLevel/ViewportWindowLevel.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/Colorbar.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/Colormap.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeLighting.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingOptions.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresets.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresetsContent.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingQuality.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShade.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShift.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevel.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevelActionMenu.tsxextensions/cornerstone/src/hooks/useViewportRendering.tsxextensions/cornerstone/src/panels/PanelSegmentation.tsxextensions/cornerstone/src/utils/ActiveViewportBehavior.tsxextensions/default/package.jsonextensions/default/src/Components/DataSourceConfigurationComponent.tsxextensions/default/src/Components/DataSourceConfigurationModalComponent.tsxextensions/default/src/Components/ItemListComponent.tsxextensions/default/src/Components/ProgressDropdownWithService.tsxextensions/default/src/DicomTagBrowser/DicomTagTable.tsxextensions/default/src/Toolbar/ToolbarLayoutSelector.tsxextensions/default/src/ViewerLayout/index.tsxextensions/default/src/customizations/workListCustomization.tsextensions/dicom-microscopy/package.jsonextensions/dicom-microscopy/src/DicomMicroscopyViewport.tsxextensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsxextensions/dicom-microscopy/src/index.tsxextensions/dicom-pdf/package.jsonextensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsxextensions/dicom-video/package.jsonextensions/measurement-tracking/package.jsonextensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsxextensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsxextensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsxextensions/test-extension/package.jsonextensions/tmtv/package.jsonextensions/tmtv/src/Panels/PanelPetSUV.tsxextensions/usAnnotation/package.jsonmodes/basic-dev-mode/package.jsonmodes/basic-test-mode/package.jsonmodes/basic/package.jsonmodes/longitudinal/package.jsonmodes/microscopy/package.jsonmodes/preclinical-4d/package.jsonmodes/segmentation/package.jsonmodes/tmtv/package.jsonmodes/usAnnotation/package.jsonpackage.jsonplatform/app/package.jsonplatform/app/src/App.tsxplatform/app/src/hooks/useStudyListStateSync.tsplatform/app/src/routes/CallbackPage.tsxplatform/app/src/routes/DataSourceWrapper.tsxplatform/app/src/routes/LegacyWorkList/LegacyWorkList.tsxplatform/app/src/routes/LegacyWorkList/filtersMeta.jsplatform/app/src/routes/LegacyWorkList/index.jsplatform/app/src/routes/Local/Local.tsxplatform/app/src/routes/Mode/Compose.tsxplatform/app/src/routes/Mode/Mode.tsxplatform/app/src/routes/NotFound/NotFound.tsxplatform/app/src/routes/SignoutCallbackComponent.tsxplatform/app/src/routes/index.tsxplatform/app/src/state/appConfig.tsxplatform/app/src/utils/preserveQueryParameters.test.tsplatform/core/package.jsonplatform/core/src/hooks/index.tsplatform/core/src/hooks/useActiveViewportDisplaySets.tsplatform/core/src/hooks/useCustomization.tsplatform/core/src/hooks/useRunCommand.tsxplatform/docs/package.jsonplatform/docs/src/pages/components/_layout/CodeBlock.tsxplatform/docs/src/pages/components/_layout/TableOfContents.tsxplatform/docs/src/theme/Footer/index.tsxplatform/i18n/package.jsonplatform/ui-next/babel.config.jsplatform/ui-next/package.jsonplatform/ui-next/src/components/Accordion/Accordion.tsxplatform/ui-next/src/components/AllInOneMenu/IconMenu.tsxplatform/ui-next/src/components/AllInOneMenu/Item.tsxplatform/ui-next/src/components/AllInOneMenu/SubMenu.tsxplatform/ui-next/src/components/Button/Button.tsxplatform/ui-next/src/components/Calendar/Calendar.tsxplatform/ui-next/src/components/Card/Card.tsxplatform/ui-next/src/components/Checkbox/Checkbox.tsxplatform/ui-next/src/components/CinePlayer/CinePlayer.tsxplatform/ui-next/src/components/Command/Command.tsxplatform/ui-next/src/components/ContextMenu/ContextMenu.tsxplatform/ui-next/src/components/DataRow/DataRow.tsxplatform/ui-next/src/components/DataTable/ActionOverlayCell.tsxplatform/ui-next/src/components/DataTable/DataTable.tsxplatform/ui-next/src/components/DataTable/useResponsiveColumns.tsxplatform/ui-next/src/components/Dialog/Dialog.tsxplatform/ui-next/src/components/Dialog/useDraggable.tsplatform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsxplatform/ui-next/src/components/DoubleSlider/DoubleSlider.tsxplatform/ui-next/src/components/DropdownMenu/DropdownMenu.tsxplatform/ui-next/src/components/HoverCard/HoverCard.tsxplatform/ui-next/src/components/Input/Input.tsxplatform/ui-next/src/components/InputFilter/InputFilter.tsxplatform/ui-next/src/components/InputMultiSelect/InputMultiSelect.tsxplatform/ui-next/src/components/InputNumber/InputNumber.tsxplatform/ui-next/src/components/InvestigationalUseDialog/InvestigationalUseDialog.tsxplatform/ui-next/src/components/Label/Label.tsxplatform/ui-next/src/components/LayoutSelector/LayoutSelector.tsxplatform/ui-next/src/components/LineChart/LineChart.tsxplatform/ui-next/src/components/LoadingIndicatorTotalPercent/LoadingIndicatorTotalPercent.tsxplatform/ui-next/src/components/NavBar/NavBar.tsxplatform/ui-next/src/components/OHIFDialogs/InputDialog.tsxplatform/ui-next/src/components/OHIFModals/UserPreferencesModal.tsxplatform/ui-next/src/components/Popover/Popover.tsxplatform/ui-next/src/components/ProgressDropdown/ProgressDiscreteBar.tsxplatform/ui-next/src/components/ProgressDropdown/ProgressDropdown.tsxplatform/ui-next/src/components/ProgressDropdown/ProgressItem.tsxplatform/ui-next/src/components/ProgressDropdown/ProgressItemDetail.tsxplatform/ui-next/src/components/ProgressDropdown/types.tsplatform/ui-next/src/components/ProgressLoadingBar/ProgressLoadingBar.tsxplatform/ui-next/src/components/ScrollArea/ScrollArea.tsxplatform/ui-next/src/components/SegmentationTable/SegmentStatistics.tsxplatform/ui-next/src/components/Select/Select.tsxplatform/ui-next/src/components/Separator/Separator.tsxplatform/ui-next/src/components/Slider/Slider.tsxplatform/ui-next/src/components/StudyBrowser/StudyBrowser.tsxplatform/ui-next/src/components/StudyItem/StudyItem.tsxplatform/ui-next/src/components/StudyList/components/Layout.tsxplatform/ui-next/src/components/StudyList/components/PreviewPatientSummary.tsxplatform/ui-next/src/components/StudyList/components/Table.tsxplatform/ui-next/src/components/Switch/Switch.tsxplatform/ui-next/src/components/Table/Table.tsxplatform/ui-next/src/components/Tabs/Tabs.tsxplatform/ui-next/src/components/Thumbnail/Thumbnail.tsxplatform/ui-next/src/components/ThumbnailList/ThumbnailList.tsxplatform/ui-next/src/components/Toggle/Toggle.tsxplatform/ui-next/src/components/ToggleGroup/ToggleGroup.tsxplatform/ui-next/src/components/ToolButton/ToolButtonList.tsxplatform/ui-next/src/components/Tooltip/Tooltip.tsxplatform/ui-next/src/components/Viewport/PatientInfo.tsxplatform/ui-next/src/components/Viewport/ViewportActionArrows.tsxplatform/ui-next/src/components/Viewport/ViewportActionBar.tsxplatform/ui-next/src/components/Viewport/ViewportActionButton.tsxplatform/ui-next/src/components/Viewport/ViewportActionCorners.tsxplatform/ui-next/src/components/Viewport/ViewportGrid.tsxplatform/ui-next/src/components/Viewport/ViewportOverlay.tsxplatform/ui-next/src/components/Viewport/ViewportPane.tsxplatform/ui-next/src/contextProviders/CineProvider.tsxplatform/ui-next/src/contextProviders/DialogProvider.tsxplatform/ui-next/src/contextProviders/DragAndDropProvider.tsxplatform/ui-next/src/contextProviders/ImageViewerProvider.tsxplatform/ui-next/src/contextProviders/ManagedDialog.tsxplatform/ui-next/src/contextProviders/NotificationProvider.tsxplatform/ui-next/src/contextProviders/UserAuthenticationProvider.tsxplatform/ui-next/src/contextProviders/ViewportDialogProvider.tsxplatform/ui-next/src/contextProviders/ViewportGridProvider.tsxplatform/ui-next/src/hooks/useDynamicMaxHeight.tsplatform/ui/package.jsonplatform/ui/src/components/InputFilterText/InputFilterText.tsxplatform/ui/src/components/Tooltip/PortalTooltip.tsxpnpm-workspace.yamlrsbuild.config.tsscripts/reactCompilerLintBudget.mjstsconfig.json
💤 Files with no reviewable changes (5)
- platform/app/src/routes/LegacyWorkList/index.js
- platform/app/src/routes/LegacyWorkList/filtersMeta.js
- platform/app/src/routes/LegacyWorkList/LegacyWorkList.tsx
- platform/ui-next/src/components/ProgressDropdown/types.ts
- extensions/default/src/customizations/workListCustomization.ts
| // New function to handle image volume loading completion | ||
| const handleImageVolumeLoadingCompleted = useCallback(() => { | ||
| const handleImageVolumeLoadingCompleted = () => { | ||
| setIsLoading(false); | ||
| updateViewportHistograms(); | ||
| }, [updateViewportHistograms]); | ||
| }; |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift
Handle volumes that are already loaded before mount.
If the image volume finishes loading before this component mounts (e.g., if the user opens the panel after the image is fully loaded), the IMAGE_VOLUME_LOADING_COMPLETED event will not fire. Consequently, isLoading will remain true forever. This will cause the setInterval (defined below) to fetch new histograms and re-render the component every second indefinitely.
Consider checking the initial load status of the viewport's volumes on mount to set isLoading to false if they are already fully loaded.
Do you want me to help generate a snippet to check the initial load status?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@extensions/cornerstone/src/components/ViewportWindowLevel/ViewportWindowLevel.tsx`
around lines 136 - 140, Update the component’s mount-time initialization around
handleImageVolumeLoadingCompleted to inspect the viewport’s image volumes’
initial load status. Set isLoading to false and perform the existing histogram
update when all relevant volumes are already fully loaded, while preserving the
event-driven handler for volumes that finish loading after mount.
| const listRef = useRef(undefined); | ||
| const canvasRef = useRef(undefined); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check for TypeScript errors in DicomTagTable.tsx
npx tsc --noEmit extensions/default/src/DicomTagBrowser/DicomTagTable.tsx || trueRepository: OHIF/Viewers
Length of output: 1275
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## File outline\n'
ast-grep outline extensions/default/src/DicomTagBrowser/DicomTagTable.tsx --view expanded || true
printf '\n## Relevant lines\n'
sed -n '1,220p' extensions/default/src/DicomTagBrowser/DicomTagTable.tsx
printf '\n## Package/type versions\n'
python3 - <<'PY'
import json, pathlib
for p in ['package.json', 'extensions/default/package.json', 'tsconfig.json']:
path = pathlib.Path(p)
if path.exists():
print(f'\n--- {p} ---')
print(path.read_text()[:4000])
PYRepository: OHIF/Viewers
Length of output: 14761
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## Search for similar useRef patterns and assignments\n'
rg -n "useRef\\(undefined\\)|\\.current\\s*=" extensions/default/src/DicomTagBrowser -S || trueRepository: OHIF/Viewers
Length of output: 403
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '220,360p' extensions/default/src/DicomTagBrowser/DicomTagTable.tsxRepository: OHIF/Viewers
Length of output: 2357
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('extensions/default/src/DicomTagBrowser/DicomTagTable.tsx')
text = p.read_text()
for needle in ['ref={listRef}', 'ref={canvasRef}', 'listRef.current', 'canvasRef.current']:
print(f'## {needle}')
for i, line in enumerate(text.splitlines(), 1):
if needle in line:
print(f'{i}: {line}')
PYRepository: OHIF/Viewers
Length of output: 590
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## React type versions\n'
rg -n '"`@types/react`"|`@types/react`' package.json pnpm-lock.yaml extensions/default/package.json -S || true
printf '\n## If installed, inspect useRef overloads\n'
for p in node_modules/@types/react/index.d.ts node_modules/react/index.d.ts; do
if [ -f "$p" ]; then
echo "--- $p ---"
rg -n "function useRef|declare function useRef|interface RefObject|interface MutableRefObject" "$p" -n -A4 -B2
fi
doneRepository: OHIF/Viewers
Length of output: 50368
Type these refs explicitly
useRef(undefined) leaves both refs as undefined, which conflicts with the List instance and canvas element used below. Use useRef<List | null>(null) and useRef<HTMLCanvasElement | null>(null).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@extensions/default/src/DicomTagBrowser/DicomTagTable.tsx` around lines 116 -
117, Update the listRef and canvasRef declarations to use explicit nullable ref
types: List | null for the List instance and HTMLCanvasElement | null for the
canvas element, initializing both with null instead of undefined. Preserve their
existing usage below.
| const CardTitle = ({ | ||
| className, | ||
| ref, | ||
| ...props | ||
| }: React.HTMLAttributes<HTMLHeadingElement> & { ref?: React.Ref<HTMLParagraphElement> }) => ( | ||
| <h3 | ||
| ref={ref} | ||
| className={cn('font-semibold leading-none tracking-tight', className)} | ||
| {...props} | ||
| /> | ||
| ); | ||
| CardTitle.displayName = 'CardTitle'; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Fix type mismatch for ref in CardTitle.
The ref prop is typed as React.Ref<HTMLParagraphElement> but it is being attached to an <h3> heading element, which expects HTMLHeadingElement. This type mismatch can cause TypeScript errors when consumers attempt to attach a valid heading ref.
💡 Proposed fix
const CardTitle = ({
className,
ref,
...props
-}: React.HTMLAttributes<HTMLHeadingElement> & { ref?: React.Ref<HTMLParagraphElement> }) => (
+}: React.HTMLAttributes<HTMLHeadingElement> & { ref?: React.Ref<HTMLHeadingElement> }) => (
<h3
ref={ref}
className={cn('font-semibold leading-none tracking-tight', className)}
{...props}
/>
);
CardTitle.displayName = 'CardTitle';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const CardTitle = ({ | |
| className, | |
| ref, | |
| ...props | |
| }: React.HTMLAttributes<HTMLHeadingElement> & { ref?: React.Ref<HTMLParagraphElement> }) => ( | |
| <h3 | |
| ref={ref} | |
| className={cn('font-semibold leading-none tracking-tight', className)} | |
| {...props} | |
| /> | |
| ); | |
| CardTitle.displayName = 'CardTitle'; | |
| const CardTitle = ({ | |
| className, | |
| ref, | |
| ...props | |
| }: React.HTMLAttributes<HTMLHeadingElement> & { ref?: React.Ref<HTMLHeadingElement> }) => ( | |
| <h3 | |
| ref={ref} | |
| className={cn('font-semibold leading-none tracking-tight', className)} | |
| {...props} | |
| /> | |
| ); | |
| CardTitle.displayName = 'CardTitle'; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@platform/ui-next/src/components/Card/Card.tsx` around lines 31 - 42, Update
the ref prop type in CardTitle from React.Ref<HTMLParagraphElement> to
React.Ref<HTMLHeadingElement> so it matches the underlying h3 element while
preserving the existing forwarded ref behavior.
| <div | ||
| ref={ref} | ||
| {...props} | ||
| data-cy="input-dialog-save-button" | ||
| onClick={() => onClick(value)} | ||
| > | ||
| <FooterAction.Primary | ||
| onClick={() => onClick(value)} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Prevent the primary action from firing twice.
A click on FooterAction.Primary invokes Line 270, then bubbles to the wrapper handler on Line 267. This can submit or save twice. Keep a single handler and place the query selector attribute on that actionable element.
Proposed fix
<div
ref={ref}
{...props}
- data-cy="input-dialog-save-button"
- onClick={() => onClick(value)}
>
<FooterAction.Primary
+ data-cy="input-dialog-save-button"
onClick={() => onClick(value)}
className={cn(className)}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div | |
| ref={ref} | |
| {...props} | |
| data-cy="input-dialog-save-button" | |
| onClick={() => onClick(value)} | |
| > | |
| <FooterAction.Primary | |
| onClick={() => onClick(value)} | |
| <div | |
| ref={ref} | |
| {...props} | |
| > | |
| <FooterAction.Primary | |
| data-cy="input-dialog-save-button" | |
| onClick={() => onClick(value)} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@platform/ui-next/src/components/OHIFDialogs/InputDialog.tsx` around lines 263
- 270, In the InputDialog action markup, remove the duplicate onClick handler
from either the wrapper div or FooterAction.Primary so clicking the primary
action invokes onClick(value) only once. Move data-cy="input-dialog-save-button"
onto the remaining actionable FooterAction.Primary element and preserve the
existing ref/props behavior on the wrapper.
| if (errors < budget.errors || warnings < budget.warnings) { | ||
| console.log('Counts are below budget. Consider tightening .react-compiler-lint-budget.json'); | ||
| console.log(`to { "errors": ${errors}, "warnings": ${warnings} } in this PR.`); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Fail when the committed budget is stale.
Only logging here leaves headroom after fixes, allowing later regressions without exceeding the budget. Exit nonzero so reductions must update the budget as promised by Lines 5-7.
Proposed fix
if (errors < budget.errors || warnings < budget.warnings) {
- console.log('Counts are below budget. Consider tightening .react-compiler-lint-budget.json');
- console.log(`to { "errors": ${errors}, "warnings": ${warnings} } in this PR.`);
+ console.error('Counts are below budget. Tighten .react-compiler-lint-budget.json');
+ console.error(`to { "errors": ${errors}, "warnings": ${warnings} } in this PR.`);
+ process.exit(1);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (errors < budget.errors || warnings < budget.warnings) { | |
| console.log('Counts are below budget. Consider tightening .react-compiler-lint-budget.json'); | |
| console.log(`to { "errors": ${errors}, "warnings": ${warnings} } in this PR.`); | |
| } | |
| if (errors < budget.errors || warnings < budget.warnings) { | |
| console.error('Counts are below budget. Tighten .react-compiler-lint-budget.json'); | |
| console.error(`to { "errors": ${errors}, "warnings": ${warnings} } in this PR.`); | |
| process.exit(1); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/reactCompilerLintBudget.mjs` around lines 43 - 46, Update the budget
comparison branch in reactCompilerLintBudget so that when errors or warnings are
below their committed budget, it exits with a nonzero status after logging the
tightening message. Preserve the existing comparison and output, and ensure the
stale-budget path fails the script as required by the documented budget
contract.
…ntime - Remove the LegacyWorkList route and the workList.variant customization; WorkList is now always mounted at / - Drop the now-unused @ohif/ui workspace dependency from 11 packages - Switch @babel/preset-react to the automatic runtime in all config blocks, matching what the rsbuild/SWC dev pipeline already produced - Set tsconfig jsx to react-jsx - Replace platform/ui-next's drifted babel.config.js with the standard re-export of the root config Claude-Session: https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
- Bump react/react-dom to 19.2.7 (exact pins) in all packages and add pnpm overrides so a single copy is guaranteed under the hoisted linker - @types/react 19.2.17, @types/react-dom 19.2.3 - @ohif/ui-next: react moves from dependencies to peerDependencies (^19) - platform/ui: react out of dependencies (UMD externals), peers widened, PortalTooltip ported from legacy ReactDOM.render to createRoot - @testing-library/react 16.3.2 (v13 relied on react-dom/test-utils, removed in react-dom 19); react-test-renderer and framer-motion deleted (zero imports) - next-themes 0.4.6, lucide-react 0.577.0, react-resize-detector 12.3.0, docs react-day-picker 9.12.0 - Apply types-react-codemod preset-19 (useRef initial values, ReactElement generics) Claude-Session: https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
The rsbuild config that already powered dev:fast is now production-ready and pnpm run build produces the app dist through it. The rspack pipeline stays available as build:legacy (rollback) and keeps powering the classic dev servers and the e2e webServer until those migrate. - Extract InjectServiceWorkerManifestPlugin into a shared file that takes its bundler APIs from compiler.webpack, so the same plugin runs under the rspack versions bundled by both pipelines - Legacy-compatible dist layout: bundles at the dist root named [name].bundle.<hash>.js, HTML emitted as index.html - Disable rsbuild's built-in publicDir copy (it copied all of public/ including config/ and html-templates/); replicate the selective copy with explicit patterns instead - Parity with webpack.base.js: optimization.sideEffects false, noParse for dicomicc, fullySpecified off for .m?js, mainFields order, prod source-map devtool template, QUICK_BUILD support, TEST_ENV define, mode-dependent APP_CONFIG default, HTML_TEMPLATE/ENTRY_TARGET envs - legalComments none to match the rspack output (no *.LICENSE.txt in dist or the sw.js precache manifest) - Add analyze script (RSDOCTOR=true) Verified against the rspack output: identical file sets modulo the documented vendor-split chunks and static/* asset layout, byte-identical app-config.js, correctly prefixed sw.js manifest under PUBLIC_URL subpaths, rollbar HTML template switch, and a served-dist smoke test. Accepted diffs: HTML is no longer minified (rsbuild 1.x needs a plugin for that) and CSS is now minified (legacy prod CSS was not). Claude-Session: https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
- babel.config.js: babel-plugin-react-compiler (target 19) runs first, covering the rspack legacy build, classic dev, jest, and the UMD package builds; REACT_COMPILER=off is the kill switch - rsbuild.config.ts: scoped @rsbuild/plugin-babel pass on workspace source (platform/extensions/modes src, excluding frozen platform/ui) layered on SWC for dev:fast and the production build; preset-typescript is reset to infer TS vs TSX per file extension because the plugin's forced isTSX rejects legal plain-.ts syntax (angle-bracket casts) - UMD package builds (ui, ui-next, core, i18n) run with REACT_COMPILER=off: their externals cover react/react-dom only, not react/compiler-runtime, so compiled output would inline React - New eslint.config.mjs (flat, eslint 10 + eslint-plugin-react-hooks 7) with the compiler-powered rules and a lint:compiler script; the diagnostics list is the do-not-touch gate for the upcoming manual memoization cleanup (baseline: 292 problems, 184 errors) - Ignore the font asset the ui-next UMD build emits at the package root Verified: compiled components present in 14 production bundles (memo_cache_sentinel), absent from the ui-next UMD dist; jest suite green with the compiler active; both dev servers boot and serve. Claude-Session: https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
…oization forwardRef -> ref-as-prop across all 28 component files (90 sites): components take ref as a regular prop typed via React.ComponentProps (which includes ref under the react 19 types) or an explicit ref?: React.Ref<...>; displayName assignments and useImperativeHandle bodies preserved. propTypes removed from all 27 files that still carried runtime prop-types (TypeScript types already cover them); the ProgressDropdownOptionPropType export and its imports removed with it. Manual memoization removed only where proven redundant: 50 useCallback/ useMemo sites across 21 files that (a) carry zero compiler-lint diagnostics and (b) were verified compiled by running the production babel transform per file and checking for memo-cache slots. Files the compiler bails on or does not recognize (factory-created components in lib/createContext, Clipboard, WorkflowsProvider, and the 22 files with compiler diagnostics) keep their manual memoization, as does the debounce-wrapping useMemo in InputFilter (recreating a debouncer per render would drop pending calls). SmartScrollbar's React.memo trio stays: SmartScrollbar.tsx fails its compiler gate. eslint.config.mjs now bans forwardRef and prop-types imports in ui-next scope so the removed patterns do not creep back in. New exhaustive-deps warnings (~18) are the classic rule not modeling compiler memoization of unwrapped effect dependencies - the affected files are all verified compiled, so effect cadence is unchanged at runtime; error-level lint surface is unchanged vs the phase-3 baseline. Claude-Session: https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
propTypes removed from the remaining 33 files across platform/app, platform/core, and all extensions (TypeScript types already cover them), and the prop-types dependency dropped from 15 package.jsons (kept in frozen platform/ui). MicroscopyPanel's interface used PropTypes members as TS types - replaced with real types. Manual memoization removed from the 18 files that pass both gates (zero compiler-lint diagnostics AND per-file verified compiler coverage): ~50 useCallback/useMemo sites across extensions/cornerstone, extensions/default, extensions/dicom-microscopy, platform/core, and platform/app hooks. The 45 other memoization-carrying files keep theirs (compiler bailouts or unrecognized components), as does ViewportWindowLevel's debounce chain. Every extension and mode UMD build script now runs REACT_COMPILER=off, matching the platform packages: their externals do not cover react/compiler-runtime, and the cornerstone extension UMD was found bundling it (verified absent after gating). Compiler-health guard rules (no forwardRef, no prop-types) now apply across the whole workspace, and a lint budget ratchet (scripts/reactCompilerLintBudget.mjs + .react-compiler-lint-budget.json, 186 errors / 135 warnings) runs in the CircleCI UNIT_TESTS job so the diagnostic count can only go down. Also removed a stale @types/react 18 entry from platform/app dependencies. Claude-Session: https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
…rfaced ManagedDialog mutated its position object in place and depended on a later render observing the mutation: compiled memoization kept explicitly positioned dialogs (e.g. the measurement context menu) clipped at the viewport edge, and the in-place mutation was also masking an infinite-setState loop in the dialog ref chain (the ref is re-attached on renders because useDraggable composes an unmemoized ref). Positions are now immutable, the state update bails out on equal coordinates, and the measure/reposition/reveal runs in a layout effect after content layout but before paint. PanelSegmentation read customizations once per render via customizationService.getCustomization, which races mode onModeEnter registrations: TMTV replaces panelSegmentation.onSegmentationAdd with its create-labelmap-from-PT handler, and when the panel's first render preceded that registration the compiler memoized the stale default handler permanently, so the segmentation was created from CT, SUV statistics (incl. lesion glycolysis) were never computed, and the TMTV CSV export crashed. The new useCustomization hook in @OHIF/core subscribes to the customization-modified events so consumers converge on the registered value regardless of mount order; PanelSegmentation now uses it for all of its customization reads. Verified with the previously failing Playwright specs: ContextMenu and TMTVCSVReport (5/5 with the compiler on). Claude-Session: https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
…tomization useViewportHover is restored to its pre-cleanup form. The sweep had unwrapped setupListeners while leaving it in the effect dependency array, so every render re-attached the document-level mousemove/resize listeners; under the resulting churn the toolbar overlay and hotkey paths intermittently never dispatched their commands (rotate/flip/reset e2e failures). The manual memoization here is load-bearing and this file is excluded from the compiler-era cleanup. useCustomization now subscribes only to MODE_CUSTOMIZATION_MODIFIED: mode-scope registrations (mode.onModeEnter) are the ones that race component mounting, while global and default customizations are registered before the app renders. PanelSegmentation reads only panelSegmentation.onSegmentationAdd through the hook - the key TMTV overrides after mount - and keeps direct getCustomization reads for the five customizations that are registered before panels can mount, which keeps the panel's re-render surface unchanged. Verified with the Playwright regression targets: ContextMenu and TMTVCSVReport pass; jest suite and production build green. Claude-Session: https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
The COVERAGE babel rule in .webpack/webpack.base.js supplied its own inline presets (@babel/preset-typescript + classic-runtime @babel/preset-react) plus istanbul. Because babel-loader still loads the root babel.config.js, that inline classic-runtime preset-react shadowed the automatic-runtime one and, with it, babel-plugin-react-compiler never took effect in coverage builds. Every COVERAGE build (Cypress e2e, the Playwright e2e webServer, and coverage unit runs) therefore shipped the compiler-era cleanup components without the memoization the compiler is supposed to restore. Context providers whose manual useMemo was removed produced a new context value every render, cascading re-renders that broke behavior the production and dev:fast builds get right - most visibly the viewport orientation markers not updating after rotate/flip/reset, which is what the OHIFCornerstoneToolbar and OHIFCornerstoneHotkeys cypress specs assert. The rule now mirrors the non-coverage dev rule: delegate to the root babel config (which carries the compiler) and add only istanbul, so the coverage/e2e build exercises the same compiled output that ships. Verified: the previously failing OHIFCornerstoneToolbar and OHIFCornerstoneHotkeys cypress specs pass (10/10) with this change. Claude-Session: https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
The BUILD_PACKAGES_QUICK security-audit step runs pnpm audit --audit-level high only when pnpm-lock.yaml changes versus master. This PR touches the lockfile (the React 19 dependency bumps), so the step runs and trips on GHSA-xcpc-8h2w-3j85 (adm-zip), a pre-existing tree entry pulled transitively through dcmjs (@cornerstonejs) that master carries but never gates. dcmjs is unchanged here (pinned 0.52.0), so this advisory is not introduced by this PR; add it to the accepted ignoreGhsas list alongside the existing entry. Claude-Session: https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
…piler Root cause of the e2e failures (OHIFCornerstoneToolbar / OHIFCornerstoneHotkeys rotate/flip/reset): the React Compiler miscompiles the components under extensions/cornerstone/src/Viewport/. Those components read and mutate external, non-React state during render and through imperative cornerstone3D event handlers (the enabled element, the camera via canvasToWorld, GL actors). The compiler's memoization assumes referential purity, so the compiled output silently stops recomputing - most visibly ViewportOrientationMarkers keeps the pre-transform letters after a rotate/flip/reset even though the command ran and the camera changed. Bisected with a deterministic oracle in a clean worktree: the failure appears exactly at the compiler-enablement commit (the prior commit passes), reproduces with the compiler on, and disappears when extensions/cornerstone/src/Viewport is excluded from it. A "use no memo" directive on the marker alone was insufficient because the miscompiled component is the viewport wrapper, so the whole Viewport directory is scoped out. The rest of the workspace keeps the compiler. Applied to both pipelines: a babel overrides-exclude for the rspack path (dev / classic dev / coverage e2e / rspack builds) and a matching @rsbuild/plugin-babel exclude for dev:fast and the rsbuild production build. Verified: rotate, flip, and reset all update the orientation markers with this exclusion (previously stale); the compiler still applies everywhere else. Claude-Session: https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
…ponent useWorkListToolbarActions invoked the ohif.dataSourceConfigurationComponent customization as a plain function, which spliced that component's hooks (useTranslation, useModal, useState/useEffect) into the caller's hook list and broke the Rules of Hooks — the WorkList route crashed with a hook-order violation and 'Cannot read properties of undefined (reading length)'. Render it as a component so its hooks get their own fiber, and gate the early return on the customization's presence instead of its render output.
ViewportSliceProgressScrollbar rebuilt imageIds and the imageIdToIndex Map on every render. The component lives in the Viewport directory that is excluded from the React Compiler, so nothing auto-memoizes them anymore; the byte-array seeding effects in useLoadedSliceBytes/useViewedSliceBytes list them as deps, re-ran each render, and their version bump re-rendered the component in an infinite loop (continuous 'Maximum update depth exceeded' errors on every viewer route). Same class of fix as the useViewportHover memoization restore.
Bump @rsbuild/core 1.7.3 -> 2.1.6, plugin-react -> 2.1.0, plugin-babel -> 2.0.1, plugin-node-polyfill -> 1.4.6. The config surface is unchanged in v2 except server.host, whose default flipped from 0.0.0.0 to localhost — pin it to keep the LAN Network URL. Rename the entry from app to index: rsbuild derives the dev-server route and printed URL from the entry name alone (only 'index' maps to '/'), so the old entry served and printed http://localhost:3000/app. The emitted bundles keep the rspack build's app.bundle.<hash>.js / app.bundle.css naming via function-form output.filename, and the index.html filename override is now redundant ([name].html already yields index.html).
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
extensions/default/src/DicomTagBrowser/DicomTagTable.tsx (1)
249-260: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick winAvoid returning unstable nested components.
The
getRowComponentfunction generates and returns a newRowListcomponent function every time it is called during render. When passed as a child toreact-window'sList, this causes all rendered rows to fully unmount and remount on every re-render, creating a severe performance bottleneck and disrupting DOM state.To resolve this, define a single stable row component and pass the dynamically changing variables through the
react-windowitemDataprop instead.⚡ Proposed fix using `itemData`
Replace
getRowComponentwith a stable component:- const getRowComponent = useCallback( - ({ rows }: { rows: Row[] }) => - (function RowList({ index, style }) { - const row = useMemo(() => rows[index], [index]); - - return ( - <RowComponent - style={style} - row={row} - keyPrefix={`DICOMTagRow-${index}`} - onToggle={onToggle(row)} - /> - ); - }), - [onToggle] - ); + const RowList = useCallback( + ({ index, style, data }) => { + const row = data.rows[index]; + return ( + <RowComponent + style={style} + row={row} + keyPrefix={`DICOMTagRow-${index}`} + onToggle={data.onToggle(row)} + /> + ); + }, + [] + ); + + const itemData = useMemo(() => ({ rows: visibleRows, onToggle }), [visibleRows, onToggle]);Then, further down in the
Listcomponent, provideitemDataand passRowListas the child:<List ref={listRef} height={500} itemCount={visibleRows.length} itemSize={getItemSize(visibleRows)} width={'100%'} itemData={itemData} className="ohif-scrollbar text-foreground" > {RowList} </List>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@extensions/default/src/DicomTagBrowser/DicomTagTable.tsx` around lines 249 - 260, Replace the per-render RowList created by getRowComponent with a stable row component defined outside that callback, receiving rows, RowComponent, and onToggle through react-window itemData. Update the List invocation to supply the corresponding itemData and pass the stable RowList directly as its child, preserving row lookup and toggle behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@extensions/tmtv/package.json`:
- Around line 30-35: Update the react entry in the peerDependencies object to
use the compatible caret range ^19.0.0, matching the platform/ui-next
convention, while leaving the other peer dependency versions unchanged.
---
Outside diff comments:
In `@extensions/default/src/DicomTagBrowser/DicomTagTable.tsx`:
- Around line 249-260: Replace the per-render RowList created by getRowComponent
with a stable row component defined outside that callback, receiving rows,
RowComponent, and onToggle through react-window itemData. Update the List
invocation to supply the corresponding itemData and pass the stable RowList
directly as its child, preserving row lookup and toggle behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d64b47bf-0b2b-45b0-8626-de9a3a73ff3a
⛔ Files ignored due to path filters (5)
.webpack/webpack.base.jsis excluded by!**/.webpack/**platform/app/.webpack/InjectServiceWorkerManifestPlugin.jsis excluded by!**/.webpack/**platform/app/.webpack/webpack.pwa.jsis excluded by!**/.webpack/**platform/ui-next/31fb9346313fc3740d7b.woff2is excluded by!**/*.woff2pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (194)
.circleci/config.yml.gitignore.react-compiler-lint-budget.jsonbabel.config.jseslint.config.mjsextensions/cornerstone-dicom-pmap/package.jsonextensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsxextensions/cornerstone-dicom-rt/package.jsonextensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsxextensions/cornerstone-dicom-seg/package.jsonextensions/cornerstone-dicom-sr/package.jsonextensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContainer.tsxextensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContentItem.tsxextensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsxextensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRTextViewport.tsxextensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRViewport.tsxextensions/cornerstone-dynamic-volume/package.jsonextensions/cornerstone/package.jsonextensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsxextensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsxextensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsxextensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsxextensions/cornerstone/src/Viewport/Overlays/ViewportSliceProgressScrollbar/ViewportSliceProgressScrollbar.tsxextensions/cornerstone/src/components/ActiveViewportWindowLevel/ActiveViewportWindowLevel.tsxextensions/cornerstone/src/components/DicomUpload/DicomUpload.tsxextensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsxextensions/cornerstone/src/components/DicomUpload/DicomUploadProgressItem.tsxextensions/cornerstone/src/components/NavigationComponent/NavigationComponent.tsxextensions/cornerstone/src/components/SegmentationUtilityButton.tsxextensions/cornerstone/src/components/SelectItemWithModality.tsxextensions/cornerstone/src/components/ViewportColorbar/ViewportColorbar.tsxextensions/cornerstone/src/components/ViewportColorbar/ViewportColorbarsContainer.tsxextensions/cornerstone/src/components/ViewportWindowLevel/ViewportWindowLevel.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/Colorbar.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/Colormap.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeLighting.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingOptions.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresets.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresetsContent.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingQuality.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShade.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShift.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevel.tsxextensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevelActionMenu.tsxextensions/cornerstone/src/hooks/useViewportRendering.tsxextensions/cornerstone/src/panels/PanelSegmentation.tsxextensions/cornerstone/src/utils/ActiveViewportBehavior.tsxextensions/default/package.jsonextensions/default/src/Components/DataSourceConfigurationComponent.tsxextensions/default/src/Components/DataSourceConfigurationModalComponent.tsxextensions/default/src/Components/ItemListComponent.tsxextensions/default/src/Components/ProgressDropdownWithService.tsxextensions/default/src/DicomTagBrowser/DicomTagTable.tsxextensions/default/src/Toolbar/ToolbarLayoutSelector.tsxextensions/default/src/ViewerLayout/index.tsxextensions/default/src/customizations/workListCustomization.tsextensions/dicom-microscopy/package.jsonextensions/dicom-microscopy/src/DicomMicroscopyViewport.tsxextensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsxextensions/dicom-microscopy/src/index.tsxextensions/dicom-pdf/package.jsonextensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsxextensions/dicom-video/package.jsonextensions/measurement-tracking/package.jsonextensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsxextensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsxextensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsxextensions/test-extension/package.jsonextensions/tmtv/package.jsonextensions/tmtv/src/Panels/PanelPetSUV.tsxextensions/usAnnotation/package.jsonmodes/basic-dev-mode/package.jsonmodes/basic-test-mode/package.jsonmodes/basic/package.jsonmodes/longitudinal/package.jsonmodes/microscopy/package.jsonmodes/preclinical-4d/package.jsonmodes/segmentation/package.jsonmodes/tmtv/package.jsonmodes/usAnnotation/package.jsonpackage.jsonplatform/app/package.jsonplatform/app/src/App.tsxplatform/app/src/hooks/useStudyListStateSync.tsplatform/app/src/hooks/useWorkListToolbarActions.tsxplatform/app/src/routes/CallbackPage.tsxplatform/app/src/routes/DataSourceWrapper.tsxplatform/app/src/routes/LegacyWorkList/LegacyWorkList.tsxplatform/app/src/routes/LegacyWorkList/filtersMeta.jsplatform/app/src/routes/LegacyWorkList/index.jsplatform/app/src/routes/Local/Local.tsxplatform/app/src/routes/Mode/Compose.tsxplatform/app/src/routes/Mode/Mode.tsxplatform/app/src/routes/NotFound/NotFound.tsxplatform/app/src/routes/SignoutCallbackComponent.tsxplatform/app/src/routes/index.tsxplatform/app/src/state/appConfig.tsxplatform/app/src/utils/preserveQueryParameters.test.tsplatform/core/package.jsonplatform/core/src/hooks/index.tsplatform/core/src/hooks/useActiveViewportDisplaySets.tsplatform/core/src/hooks/useCustomization.tsplatform/core/src/hooks/useRunCommand.tsxplatform/docs/package.jsonplatform/docs/src/pages/components/_layout/CodeBlock.tsxplatform/docs/src/pages/components/_layout/TableOfContents.tsxplatform/docs/src/theme/Footer/index.tsxplatform/i18n/package.jsonplatform/ui-next/babel.config.jsplatform/ui-next/package.jsonplatform/ui-next/src/components/Accordion/Accordion.tsxplatform/ui-next/src/components/AllInOneMenu/IconMenu.tsxplatform/ui-next/src/components/AllInOneMenu/Item.tsxplatform/ui-next/src/components/AllInOneMenu/SubMenu.tsxplatform/ui-next/src/components/Button/Button.tsxplatform/ui-next/src/components/Calendar/Calendar.tsxplatform/ui-next/src/components/Card/Card.tsxplatform/ui-next/src/components/Checkbox/Checkbox.tsxplatform/ui-next/src/components/CinePlayer/CinePlayer.tsxplatform/ui-next/src/components/Command/Command.tsxplatform/ui-next/src/components/ContextMenu/ContextMenu.tsxplatform/ui-next/src/components/DataRow/DataRow.tsxplatform/ui-next/src/components/DataTable/ActionOverlayCell.tsxplatform/ui-next/src/components/DataTable/DataTable.tsxplatform/ui-next/src/components/DataTable/useResponsiveColumns.tsxplatform/ui-next/src/components/Dialog/Dialog.tsxplatform/ui-next/src/components/Dialog/useDraggable.tsplatform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsxplatform/ui-next/src/components/DoubleSlider/DoubleSlider.tsxplatform/ui-next/src/components/DropdownMenu/DropdownMenu.tsxplatform/ui-next/src/components/HoverCard/HoverCard.tsxplatform/ui-next/src/components/Input/Input.tsxplatform/ui-next/src/components/InputFilter/InputFilter.tsxplatform/ui-next/src/components/InputMultiSelect/InputMultiSelect.tsxplatform/ui-next/src/components/InputNumber/InputNumber.tsxplatform/ui-next/src/components/InvestigationalUseDialog/InvestigationalUseDialog.tsxplatform/ui-next/src/components/Label/Label.tsxplatform/ui-next/src/components/LayoutSelector/LayoutSelector.tsxplatform/ui-next/src/components/LineChart/LineChart.tsxplatform/ui-next/src/components/LoadingIndicatorTotalPercent/LoadingIndicatorTotalPercent.tsxplatform/ui-next/src/components/NavBar/NavBar.tsxplatform/ui-next/src/components/OHIFDialogs/InputDialog.tsxplatform/ui-next/src/components/OHIFModals/UserPreferencesModal.tsxplatform/ui-next/src/components/Popover/Popover.tsxplatform/ui-next/src/components/ProgressDropdown/ProgressDiscreteBar.tsxplatform/ui-next/src/components/ProgressDropdown/ProgressDropdown.tsxplatform/ui-next/src/components/ProgressDropdown/ProgressItem.tsxplatform/ui-next/src/components/ProgressDropdown/ProgressItemDetail.tsxplatform/ui-next/src/components/ProgressDropdown/types.tsplatform/ui-next/src/components/ProgressLoadingBar/ProgressLoadingBar.tsxplatform/ui-next/src/components/ScrollArea/ScrollArea.tsxplatform/ui-next/src/components/SegmentationTable/SegmentStatistics.tsxplatform/ui-next/src/components/Select/Select.tsxplatform/ui-next/src/components/Separator/Separator.tsxplatform/ui-next/src/components/Slider/Slider.tsxplatform/ui-next/src/components/StudyBrowser/StudyBrowser.tsxplatform/ui-next/src/components/StudyItem/StudyItem.tsxplatform/ui-next/src/components/StudyList/components/Layout.tsxplatform/ui-next/src/components/StudyList/components/PreviewPatientSummary.tsxplatform/ui-next/src/components/StudyList/components/Table.tsxplatform/ui-next/src/components/Switch/Switch.tsxplatform/ui-next/src/components/Table/Table.tsxplatform/ui-next/src/components/Tabs/Tabs.tsxplatform/ui-next/src/components/Thumbnail/Thumbnail.tsxplatform/ui-next/src/components/ThumbnailList/ThumbnailList.tsxplatform/ui-next/src/components/Toggle/Toggle.tsxplatform/ui-next/src/components/ToggleGroup/ToggleGroup.tsxplatform/ui-next/src/components/ToolButton/ToolButtonList.tsxplatform/ui-next/src/components/Tooltip/Tooltip.tsxplatform/ui-next/src/components/Viewport/PatientInfo.tsxplatform/ui-next/src/components/Viewport/ViewportActionArrows.tsxplatform/ui-next/src/components/Viewport/ViewportActionBar.tsxplatform/ui-next/src/components/Viewport/ViewportActionButton.tsxplatform/ui-next/src/components/Viewport/ViewportActionCorners.tsxplatform/ui-next/src/components/Viewport/ViewportGrid.tsxplatform/ui-next/src/components/Viewport/ViewportOverlay.tsxplatform/ui-next/src/components/Viewport/ViewportPane.tsxplatform/ui-next/src/contextProviders/CineProvider.tsxplatform/ui-next/src/contextProviders/DialogProvider.tsxplatform/ui-next/src/contextProviders/DragAndDropProvider.tsxplatform/ui-next/src/contextProviders/ImageViewerProvider.tsxplatform/ui-next/src/contextProviders/ManagedDialog.tsxplatform/ui-next/src/contextProviders/NotificationProvider.tsxplatform/ui-next/src/contextProviders/UserAuthenticationProvider.tsxplatform/ui-next/src/contextProviders/ViewportDialogProvider.tsxplatform/ui-next/src/contextProviders/ViewportGridProvider.tsxplatform/ui-next/src/hooks/useDynamicMaxHeight.tsplatform/ui/package.jsonplatform/ui/src/components/InputFilterText/InputFilterText.tsxplatform/ui/src/components/Tooltip/PortalTooltip.tsxpnpm-workspace.yamlrsbuild.config.tsscripts/reactCompilerLintBudget.mjstsconfig.json
💤 Files with no reviewable changes (5)
- platform/app/src/routes/LegacyWorkList/index.js
- platform/app/src/routes/LegacyWorkList/LegacyWorkList.tsx
- platform/app/src/routes/LegacyWorkList/filtersMeta.js
- platform/ui-next/src/components/ProgressDropdown/types.ts
- extensions/default/src/customizations/workListCustomization.ts
🚧 Files skipped from review as they are similar to previous changes (163)
- extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShift.tsx
- platform/app/src/routes/Local/Local.tsx
- extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeLighting.tsx
- extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingQuality.tsx
- platform/ui-next/src/components/LoadingIndicatorTotalPercent/LoadingIndicatorTotalPercent.tsx
- platform/app/src/utils/preserveQueryParameters.test.ts
- platform/docs/src/pages/components/_layout/TableOfContents.tsx
- platform/docs/src/pages/components/_layout/CodeBlock.tsx
- platform/core/src/hooks/index.ts
- modes/tmtv/package.json
- extensions/default/src/Components/DataSourceConfigurationModalComponent.tsx
- platform/ui-next/src/hooks/useDynamicMaxHeight.ts
- platform/ui-next/babel.config.js
- platform/core/src/hooks/useCustomization.ts
- platform/app/src/routes/index.tsx
- extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
- platform/ui-next/src/components/ProgressLoadingBar/ProgressLoadingBar.tsx
- extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevel.tsx
- .gitignore
- platform/ui-next/src/components/Slider/Slider.tsx
- tsconfig.json
- extensions/default/src/Components/DataSourceConfigurationComponent.tsx
- modes/usAnnotation/package.json
- extensions/cornerstone-dicom-seg/package.json
- extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
- extensions/cornerstone/src/components/WindowLevelActionMenu/Colormap.tsx
- platform/app/src/routes/Mode/Mode.tsx
- modes/segmentation/package.json
- extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContainer.tsx
- extensions/cornerstone/src/components/ActiveViewportWindowLevel/ActiveViewportWindowLevel.tsx
- extensions/cornerstone/src/components/SegmentationUtilityButton.tsx
- platform/ui-next/src/contextProviders/ImageViewerProvider.tsx
- pnpm-workspace.yaml
- platform/ui/src/components/InputFilterText/InputFilterText.tsx
- .circleci/config.yml
- platform/ui-next/src/components/StudyList/components/PreviewPatientSummary.tsx
- extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx
- platform/ui-next/src/components/Button/Button.tsx
- platform/ui-next/src/components/StudyList/components/Table.tsx
- platform/ui-next/src/components/DataTable/ActionOverlayCell.tsx
- scripts/reactCompilerLintBudget.mjs
- platform/ui-next/src/components/AllInOneMenu/Item.tsx
- extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx
- platform/ui-next/src/components/Checkbox/Checkbox.tsx
- platform/ui-next/src/components/Switch/Switch.tsx
- platform/docs/src/theme/Footer/index.tsx
- extensions/usAnnotation/package.json
- extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx
- platform/ui-next/src/components/Tooltip/Tooltip.tsx
- modes/preclinical-4d/package.json
- modes/basic-dev-mode/package.json
- modes/basic/package.json
- platform/app/src/routes/DataSourceWrapper.tsx
- platform/core/src/hooks/useRunCommand.tsx
- platform/ui-next/src/components/SegmentationTable/SegmentStatistics.tsx
- extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx
- platform/ui-next/src/components/Viewport/ViewportActionButton.tsx
- extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContentItem.tsx
- extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx
- .react-compiler-lint-budget.json
- extensions/cornerstone/src/components/ViewportColorbar/ViewportColorbarsContainer.tsx
- extensions/default/src/ViewerLayout/index.tsx
- modes/basic-test-mode/package.json
- platform/ui-next/src/components/Label/Label.tsx
- extensions/dicom-microscopy/package.json
- platform/ui-next/src/contextProviders/NotificationProvider.tsx
- platform/ui-next/src/components/HoverCard/HoverCard.tsx
- platform/core/package.json
- platform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx
- platform/ui-next/src/components/Viewport/PatientInfo.tsx
- platform/app/src/routes/SignoutCallbackComponent.tsx
- platform/ui/src/components/Tooltip/PortalTooltip.tsx
- extensions/cornerstone/src/components/SelectItemWithModality.tsx
- platform/ui-next/src/components/Viewport/ViewportActionArrows.tsx
- platform/ui-next/src/components/InvestigationalUseDialog/InvestigationalUseDialog.tsx
- extensions/cornerstone-dicom-rt/package.json
- extensions/dicom-microscopy/src/index.tsx
- extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevelActionMenu.tsx
- babel.config.js
- platform/ui-next/src/components/Separator/Separator.tsx
- extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx
- platform/ui-next/src/components/Dialog/useDraggable.ts
- platform/ui-next/src/components/StudyList/components/Layout.tsx
- modes/longitudinal/package.json
- extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx
- platform/app/src/hooks/useStudyListStateSync.ts
- extensions/tmtv/src/Panels/PanelPetSUV.tsx
- platform/app/src/routes/Mode/Compose.tsx
- extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx
- platform/app/src/routes/NotFound/NotFound.tsx
- extensions/default/package.json
- extensions/dicom-pdf/package.json
- platform/ui-next/src/components/AllInOneMenu/SubMenu.tsx
- platform/app/src/routes/CallbackPage.tsx
- extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresets.tsx
- platform/ui-next/src/components/LayoutSelector/LayoutSelector.tsx
- platform/docs/package.json
- platform/ui-next/src/components/Viewport/ViewportActionBar.tsx
- platform/ui-next/src/components/Viewport/ViewportActionCorners.tsx
- platform/i18n/package.json
- extensions/cornerstone/src/components/DicomUpload/DicomUploadProgressItem.tsx
- extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx
- platform/ui-next/src/components/Viewport/ViewportPane.tsx
- extensions/cornerstone/src/hooks/useViewportRendering.tsx
- platform/ui-next/src/components/InputMultiSelect/InputMultiSelect.tsx
- platform/ui-next/src/components/ProgressDropdown/ProgressItem.tsx
- extensions/cornerstone-dicom-pmap/package.json
- platform/ui-next/src/components/Calendar/Calendar.tsx
- platform/ui-next/src/contextProviders/ViewportDialogProvider.tsx
- platform/ui-next/src/components/CinePlayer/CinePlayer.tsx
- platform/ui-next/src/components/Tabs/Tabs.tsx
- platform/ui/package.json
- platform/ui-next/src/components/LineChart/LineChart.tsx
- platform/ui-next/src/components/Viewport/ViewportOverlay.tsx
- platform/ui-next/src/components/InputFilter/InputFilter.tsx
- platform/ui-next/src/components/OHIFModals/UserPreferencesModal.tsx
- platform/ui-next/src/components/ScrollArea/ScrollArea.tsx
- platform/ui-next/src/components/DataTable/useResponsiveColumns.tsx
- platform/app/src/App.tsx
- extensions/test-extension/package.json
- package.json
- platform/ui-next/src/components/Viewport/ViewportGrid.tsx
- platform/ui-next/src/contextProviders/DialogProvider.tsx
- platform/ui-next/src/components/DataTable/DataTable.tsx
- platform/ui-next/src/contextProviders/DragAndDropProvider.tsx
- extensions/cornerstone/src/Viewport/Overlays/ViewportSliceProgressScrollbar/ViewportSliceProgressScrollbar.tsx
- platform/ui-next/src/components/DataRow/DataRow.tsx
- platform/ui-next/src/components/StudyItem/StudyItem.tsx
- extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresetsContent.tsx
- platform/ui-next/src/components/ThumbnailList/ThumbnailList.tsx
- platform/ui-next/src/components/Card/Card.tsx
- extensions/default/src/Components/ProgressDropdownWithService.tsx
- platform/app/package.json
- extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx
- extensions/cornerstone-dicom-sr/package.json
- extensions/measurement-tracking/package.json
- extensions/cornerstone/src/components/WindowLevelActionMenu/Colorbar.tsx
- platform/ui-next/src/contextProviders/ManagedDialog.tsx
- platform/ui-next/src/components/Popover/Popover.tsx
- platform/ui-next/src/components/Thumbnail/Thumbnail.tsx
- extensions/cornerstone/package.json
- extensions/cornerstone/src/components/ViewportWindowLevel/ViewportWindowLevel.tsx
- extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx
- platform/ui-next/src/components/OHIFDialogs/InputDialog.tsx
- extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRTextViewport.tsx
- platform/app/src/state/appConfig.tsx
- platform/ui-next/src/contextProviders/ViewportGridProvider.tsx
- extensions/cornerstone/src/utils/ActiveViewportBehavior.tsx
- platform/ui-next/src/components/ToolButton/ToolButtonList.tsx
- platform/ui-next/src/components/StudyBrowser/StudyBrowser.tsx
- platform/ui-next/src/contextProviders/UserAuthenticationProvider.tsx
- platform/ui-next/src/components/ProgressDropdown/ProgressItemDetail.tsx
- platform/ui-next/src/components/Dialog/Dialog.tsx
- platform/ui-next/src/components/DropdownMenu/DropdownMenu.tsx
- platform/core/src/hooks/useActiveViewportDisplaySets.ts
- extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRViewport.tsx
- platform/ui-next/src/components/ContextMenu/ContextMenu.tsx
- extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx
- extensions/cornerstone-dynamic-volume/package.json
- extensions/dicom-video/package.json
- eslint.config.mjs
- platform/ui-next/src/components/Command/Command.tsx
- extensions/cornerstone/src/components/ViewportColorbar/ViewportColorbar.tsx
| "clean": "shx rm -rf dist", | ||
| "clean:deep": "pnpm run clean && shx rm -rf node_modules", | ||
| "dev": "cross-env NODE_ENV=development rspack build --config .webpack/webpack.dev.js --watch", | ||
| "dev": "cross-env REACT_COMPILER=off NODE_ENV=development rspack build --config .webpack/webpack.dev.js --watch", |
There was a problem hiding this comment.
Where is this getting used? It feels like REACT_COMPILER=off should be part of the webpack.dev.js configuration rather than being passed in. Passing it in this way will cause bugs if someone adds a new build command and forgets to add it.
There was a problem hiding this comment.
I didn't catch this, i think react compiler should be on by default it is in production for couple of months by Meta apps and stable should work
wayfarer3130
left a comment
There was a problem hiding this comment.
I've looked over the changes, made a couple of targetted fixes for a few things.
Still needs review by @dan-rukas and @jbocce
Generally think this looks good.
| "keywords": [] | ||
| "keywords": [], | ||
| "peerDependencies": { | ||
| "react": "^19.0.0", |
There was a problem hiding this comment.
I think we want exact versioning for consistency sake. However I do believe that now that we have forced frozen lockfiles with pnpm this becomes less of a concern. But since we already do this across the board, let's use exact versioning please.
What this PR does
Upgrades the monorepo to React 19 with the React Compiler enabled across every build pipeline, modernizes the codebase to React 19 idioms, and completes the webpack exit by moving the application production build to rsbuild.
React 19.2.7
react/react-dombumped to 19.2.7 (exact pins) in every package, withpnpm-workspace.yamloverrides guaranteeing a single copy under the hoisted linker.@types/react19.2.17 /@types/react-dom19.2.3;types-react-codemod preset-19applied (bareuseRef(),ReactElementgenerics).@testing-library/react16.3.2 (v13 depended onreact-dom/test-utils, removed in react-dom 19).react-test-rendererandframer-motiondeleted (zero imports).next-themes0.4.6,lucide-react0.577.0,react-resize-detector12.3.0.@ohif/ui-nextnow declaresreact/react-domaspeerDependencies(^19) instead of dependencies. External consumers of the published package must be on React 19.@ohif/uileaves the app graph entirely: the LegacyWorkList route and theworkList.variantcustomization are removed (the new WorkList is always mounted at/), and the stale@ohif/uiworkspace dependency is dropped from 11 packages. The frozen package still builds and publishes; itsPortalTooltipwas ported off the removed legacyReactDOM.renderAPI.preset-reactruntime: automatic, tsconfigjsx: react-jsx).React Compiler
babel-plugin-react-compiler@1.0.0(target 19) runs first in the root babel config (rspack dev server, jest, package builds) and as a scoped@rsbuild/plugin-babelpass over workspace source in the rsbuild dev/prod builds.REACT_COMPILER=offis a global kill switch. All UMD package builds run with it set: their externals coverreact/react-dombut notreact/compiler-runtime, and compiled output would otherwise inline React internals (verified leak in the cornerstone extension UMD before gating; verified absent after). This means these UMD builds should not be used for running OHIF since the automatic memoization is excluded, but this is safe in the context since the actual builds don't use this.eslint 10+eslint-plugin-react-hooks 7.1.1compiler rules) behindpnpm lint:compiler, and a budget file +scripts/reactCompilerLintBudget.mjswired into CircleCI so the diagnostic count can only go down (currently 186 errors / 135 warnings, all pre-existing rule-of-react violations).Compiler-era cleanup
forwardRef->ref-as-prop across all 28 ui-next component files (90 sites);React.ComponentPropscarriesrefunder the React 19 types.useImperativeHandlesites preserved.propTypesremoved everywhere in the app graph (60 files) along with theprop-typesdependency (kept in frozen@ohif/ui).useCallback/useMemosites across 39 files that (a) carry zero compiler-lint diagnostics and (b) were verified compiled by running the production babel transform per file and checking for memo-cache slots. Files the compiler bails on keep their memoization untouched (includingSmartScrollbar'sReact.memotrio and every debounce-wrapping memo).forwardRefusage orprop-typesimports workspace-wide.rsbuild production build (webpack exit for the app)
pnpm run buildnow produces the app through the same rsbuild config that already powereddev:fast; the rspack pipeline remains asbuild:legacyand still powers the classic dev servers and the e2e webServer.static/*asset layout), byte-identicalapp-config.js, correctsw.jsprecache manifest incl.PUBLIC_URLsubpath builds,HTML_TEMPLATE/QUICK_BUILD/ENTRY_TARGETenv parity, and a served-dist smoke test. The service-worker manifest plugin is now shared between both pipelines.Bugs surfaced by the compiler and fixed properly
Two latent mutation/timing bugs became visible once components were compiled; both are fixed in ordering-independent ways rather than by suppressing the compiler:
ManagedDialogmutated its position object in place and relied on a later render to observe the mutation. Compiled memoization kept dialogs (e.g. the measurement context menu) clipped at the viewport edge, and the mutation was masking an infinite-setState loop in the dialog ref chain. Positions are now immutable with a value-equality bailout, and measurement happens in a layout effect before paint.PanelSegmentationread customizations once per render viacustomizationService.getCustomization, racing modeonModeEnterregistrations (TMTV's PT-labelmap override could land after the first read; the compiler then memoized the stale default forever, breaking the TMTV CSV export). A newuseCustomizationhook in@ohif/coresubscribes toMODE_CUSTOMIZATION_MODIFIED(the scope whose registrations race mounting) and the panel readspanelSegmentation.onSegmentationAddthrough it.useViewportHover's sweep had unwrappedsetupListenerswhile leaving it in the effect dependency array, re-attaching document-level listeners every render; under that churn the toolbar overlay/hotkey paths intermittently never dispatched their commands (Cypress rotate/flip/reset failures, bisected to the file and verified fixed by restoring it). The manual memoization there is load-bearing and the file is excluded from the cleanup.React Compiler scope: cornerstone viewport components excluded
The compiler is enabled workspace-wide with one exclusion:
extensions/cornerstone/src/Viewport/. Those components read and mutateexternal, non-React cornerstone3D state during render and in imperative event
handlers (the enabled element, the camera via
canvasToWorld, GL actors). Thecompiler's memoization assumes referential purity, so compiling them silently
drops updates -
ViewportOrientationMarkerskept its pre-transform lettersafter rotate/flip/reset even though the command ran and the camera changed. The
directory is scoped out on both pipelines (a babel
overridesexclude for therspack path and a matching
@rsbuild/plugin-babelexclude for rsbuild); therest of the workspace keeps the compiler. This was bisected to the
compiler-enablement commit with a deterministic e2e oracle and verified fixed.
Testing
useViewportHoversweep and pass with it restored. Late-session local reruns became unreliable for these two specs in a way that also failed pristine master on the same machine (locked-session rendering throttling), so CI is the authoritative check for the final state.build:package-all(all ~30 UMD builds) green with the compiler gates; no UMD containsreact/compiler-runtime.Follow-ups (deliberately out of scope)
i18next-stack modernization, zustand 5 / react-router 7, HTML minification plugin for the rsbuild build, and fixing the ~180 pre-existing rules-of-react violations the compiler lint surfaced (each fix unlocks compilation and further cleanup for that component).
https://claude.ai/code/session_01KKhdKHKU26suaTRHFVyXdR
Summary by CodeRabbit
New Features
useCustomizationhook for automatic customization updates.Changes
createRootAPI.Bug Fixes