Skip to content

feat: React 19 + React Compiler, compiler-era cleanup, and rsbuild production build - #6164

Open
sedghi wants to merge 16 commits into
masterfrom
ohifReact
Open

feat: React 19 + React Compiler, compiler-era cleanup, and rsbuild production build#6164
sedghi wants to merge 16 commits into
masterfrom
ohifReact

Conversation

@sedghi

@sedghi sedghi commented Jul 18, 2026

Copy link
Copy Markdown
Member

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-dom bumped to 19.2.7 (exact pins) in every package, with pnpm-workspace.yaml overrides guaranteeing a single copy under the hoisted linker.
  • @types/react 19.2.17 / @types/react-dom 19.2.3; types-react-codemod preset-19 applied (bare useRef(), ReactElement generics).
  • @testing-library/react 16.3.2 (v13 depended 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.
  • @ohif/ui-next now declares react/react-dom as peerDependencies (^19) instead of dependencies. External consumers of the published package must be on React 19.
  • Legacy @ohif/ui leaves the app graph entirely: the LegacyWorkList route and the workList.variant customization are removed (the new WorkList is always mounted at /), and the stale @ohif/ui workspace dependency is dropped from 11 packages. The frozen package still builds and publishes; its PortalTooltip was ported off the removed legacy ReactDOM.render API.
  • Both JSX pipelines converge on the automatic runtime (babel preset-react runtime: automatic, tsconfig jsx: 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-babel pass over workspace source in the rsbuild dev/prod builds.
  • REACT_COMPILER=off is a global kill switch. All UMD package builds run with it set: their externals cover react/react-dom but not react/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.
  • Compiler health is measurable and ratcheted: a dedicated flat ESLint config (eslint 10 + eslint-plugin-react-hooks 7.1.1 compiler rules) behind pnpm lint:compiler, and a budget file + scripts/reactCompilerLintBudget.mjs wired 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.ComponentProps carries ref under the React 19 types. useImperativeHandle sites preserved.
  • Runtime propTypes removed everywhere in the app graph (60 files) along with the prop-types dependency (kept in frozen @ohif/ui).
  • Manual memoization removed only where provably redundant: ~100 useCallback/useMemo sites 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 (including SmartScrollbar's React.memo trio and every debounce-wrapping memo).
  • ESLint guards now error on new forwardRef usage or prop-types imports workspace-wide.

rsbuild production build (webpack exit for the app)

  • pnpm run build now produces the app through the same rsbuild config that already powered dev:fast; the rspack pipeline remains as build:legacy and still powers the classic dev servers and the e2e webServer.
  • Parity with the rspack output was verified file-by-file: identical dist file sets (modulo documented vendor-split chunks and static/* asset layout), byte-identical app-config.js, correct sw.js precache manifest incl. PUBLIC_URL subpath builds, HTML_TEMPLATE/QUICK_BUILD/ENTRY_TARGET env parity, and a served-dist smoke test. The service-worker manifest plugin is now shared between both pipelines.
  • Two accepted differences: HTML is no longer minified (rsbuild 1.x needs a plugin for that) and CSS is now minified (the legacy prod CSS was not).

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:

  • ManagedDialog mutated 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.
  • PanelSegmentation read customizations once per render via customizationService.getCustomization, racing mode onModeEnter registrations (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 new useCustomization hook in @ohif/core subscribes to MODE_CUSTOMIZATION_MODIFIED (the scope whose registrations race mounting) and the panel reads panelSegmentation.onSegmentationAdd through it.
  • One cleanup-wave edit was reverted: useViewportHover's sweep had unwrapped setupListeners while 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 mutate
external, non-React cornerstone3D state during render and in imperative event
handlers (the enabled element, the camera via canvasToWorld, GL actors). The
compiler's memoization assumes referential purity, so compiling them silently
drops updates - ViewportOrientationMarkers kept its pre-transform letters
after rotate/flip/reset even though the command ran and the camera changed. The
directory is scoped out on both pipelines (a babel overrides exclude for the
rspack path and a matching @rsbuild/plugin-babel exclude for rsbuild); the
rest of the workspace keeps the compiler. This was bisected to the
compiler-enablement commit with a deterministic e2e oracle and verified fixed.

Testing

  • Playwright (chromium, local): 171 passed / 5 skipped, including the previously failing ContextMenu and TMTV CSV specs (TMTV verified 5/5 after the fix). The one remaining failure is the DicomTagBrowser scrollbar look-and-feel screenshot, which diffs only in scrollbar-thumb pixels and passed 3/3 in isolation earlier in the session - macOS screenshot flake, deferred to CI's Linux baselines.
  • Cypress (local): 13/15 specs green; the two failing specs (toolbar/hotkey rotate-flip-reset) were bisected to the useViewportHover sweep 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.
  • Jest: 92 suites / 1098 tests green with the compiler active.
  • build:package-all (all ~30 UMD builds) green with the compiler gates; no UMD contains react/compiler-runtime.
  • Docs (Docusaurus 3.10) build green against post-codemod ui-next source.

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

    • Added React 19 support across the platform and extensions.
    • Introduced a useCustomization hook for automatic customization updates.
  • Changes

    • Updated routing to consistently use the standard Work List (legacy work list route removed).
    • Removed runtime prop-type validation across the app and UI components; modernized typing and simplified several UI wrappers.
    • Updated portal tooltip rendering to use React’s modern createRoot API.
    • Added React Compiler “lint budget” checks to CI, and disabled the React compiler in build/dev scripts.
  • Bug Fixes

    • Improved initial ref/nullable handling to prevent undefined ref edge cases.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@sedghi
sedghi had a problem deploying to fork-pr-approval July 18, 2026 07:40 — with GitHub Actions Failure
@netlify

netlify Bot commented Jul 18, 2026

Copy link
Copy Markdown

Deploy Preview for ohif-dev ready!

Name Link
🔨 Latest commit 15106b5
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6a68d81e02eb620008a87775
😎 Deploy Preview https://deploy-preview-6164--ohif-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

React 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.

Changes

React Compiler and build tooling

Layer / File(s) Summary
Compiler linting and build integration
.circleci/config.yml, eslint.config.mjs, babel.config.js, rsbuild.config.ts, scripts/reactCompilerLintBudget.mjs
Adds Compiler-aware Babel and ESLint configuration, lint-budget enforcement, CI execution, automatic JSX runtime support, and environment-aware rsbuild configuration.
React 19 package contracts
package.json, pnpm-workspace.yaml, platform/*/package.json, extensions/*/package.json, modes/*/package.json
Updates React versions and overrides, adds compiler-off build flags, adjusts dependencies, and adds compiler/build tooling.

Application and component migration

Layer / File(s) Summary
Application and core migration
platform/app/src/*, platform/core/src/hooks/*, platform/ui/src/components/Tooltip/PortalTooltip.tsx
Removes legacy worklist routing and PropTypes, adds reactive customization lookup, updates state synchronization, and migrates portal rendering to createRoot.
UI component migration
platform/ui-next/src/components/*, platform/ui-next/src/contextProviders/*, platform/ui-next/src/hooks/*, extensions/*/src/**
Replaces selected forwardRef, memoization, and runtime PropTypes patterns; updates ref and React element typings; and initializes refs explicitly.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description is substantial, but it does not follow the required template sections and leaves the checklist items unchecked. Reformat it to the repo template with Context, Changes & Results, Testing, and Checklist sections, and mark the required checkboxes.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise, descriptive, and accurately summarizes the main changes: React 19, compiler enablement, cleanup, and rsbuild build migration.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ohifReact

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cypress

cypress Bot commented Jul 18, 2026

Copy link
Copy Markdown

Viewers    Run #6604

Run Properties:  status check passed Passed #6604  •  git commit 15106b56ed: Fixes for various PR issues
Project Viewers
Branch Review ohifReact
Run status status check passed Passed #6604
Run duration 02m 01s
Commit git commit 15106b56ed: Fixes for various PR issues
Committer Bill Wallace
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 28
View all changes introduced in this branch ↗︎

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Keep these callbacks stable in effect dependencies

  • platform/ui-next/src/components/InputMultiSelect/InputMultiSelect.tsx#L112-L140: measure is recreated on every render, so the layout effect runs again after setCoords schedules a render and can loop while the popup is open.
  • platform/ui-next/src/components/ScrollArea/ScrollArea.tsx#L45-L57: checkScroll should 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 win

Preserve the Babel preset’s unrelated defaults.

Replacing the options with {} also drops defaults such as allowDeclareFields, allowNamespaces, and optimizeConstEnums; only allExtensions and isTSX need 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 win

Restrict the forwardRef import, 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 win

Replace removed PropTypes with TypeScript prop contracts.

These TypeScript components now expose implicitly any props after their runtime schemas were deleted.

  • platform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx#L1-L1: define the messages and id contract.
  • 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, and showPatientInfoRef.
  • 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: type onArrowsClick and className.
  • 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 win

Keep checkScroll stable 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 win

Use 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 style prop 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 win

Consider using useCustomization for the other customization keys to ensure reactivity.

Currently, only panelSegmentation.onSegmentationAdd utilizes the useCustomization hook to protect against stale data when dynamically overridden. Direct calls to getCustomization for 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 win

Consider using useSyncExternalStore for 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 the setValue(() => ...) functional updater workaround).

Since getCustomization returns a stable reference for unchanged values, useSyncExternalStore is 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 value

Remove stale comments referencing removed manual memoization.

Since useCallback and useMemo wrappers 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 it comment above getDisplaySetsForViewport.
  • platform/core/src/hooks/useActiveViewportDisplaySets.ts#L51-L55: remove or update the // Only depend on stable references comment next to the dependency array.
  • extensions/cornerstone/src/components/ViewportWindowLevel/ViewportWindowLevel.tsx#L175-L176: reword // Create a memoized version of displaySet IDs for comparison to 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 tradeoff

Extract RowList and use itemData to prevent unmounting.

Defining RowList inline as a returned component causes its reference to change whenever getRowComponent is called. When passed to react-window's <List>, this forces React to unmount and remount every visible row on every render, destroying DOM nodes and resetting internal hook states (like useMemo). Furthermore, the React Compiler strictly forbids defining components with hooks inside other functions.

Consider extracting RowList outside of DicomTagTable and using react-window's itemData prop to pass rows and onToggle.

♻️ 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5611f97 and ad6df50.

⛔ Files ignored due to path filters (4)
  • platform/app/.webpack/InjectServiceWorkerManifestPlugin.js is excluded by !**/.webpack/**
  • platform/app/.webpack/webpack.pwa.js is excluded by !**/.webpack/**
  • platform/ui-next/31fb9346313fc3740d7b.woff2 is excluded by !**/*.woff2
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (193)
  • .circleci/config.yml
  • .gitignore
  • .react-compiler-lint-budget.json
  • babel.config.js
  • eslint.config.mjs
  • extensions/cornerstone-dicom-pmap/package.json
  • extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx
  • extensions/cornerstone-dicom-rt/package.json
  • extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx
  • extensions/cornerstone-dicom-seg/package.json
  • extensions/cornerstone-dicom-sr/package.json
  • extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContainer.tsx
  • extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContentItem.tsx
  • extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx
  • extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRTextViewport.tsx
  • extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRViewport.tsx
  • extensions/cornerstone-dynamic-volume/package.json
  • extensions/cornerstone/package.json
  • extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
  • extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx
  • extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx
  • extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx
  • extensions/cornerstone/src/Viewport/Overlays/ViewportSliceProgressScrollbar/ViewportSliceProgressScrollbar.tsx
  • extensions/cornerstone/src/components/ActiveViewportWindowLevel/ActiveViewportWindowLevel.tsx
  • extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx
  • extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx
  • extensions/cornerstone/src/components/DicomUpload/DicomUploadProgressItem.tsx
  • extensions/cornerstone/src/components/NavigationComponent/NavigationComponent.tsx
  • extensions/cornerstone/src/components/SegmentationUtilityButton.tsx
  • extensions/cornerstone/src/components/SelectItemWithModality.tsx
  • extensions/cornerstone/src/components/ViewportColorbar/ViewportColorbar.tsx
  • extensions/cornerstone/src/components/ViewportColorbar/ViewportColorbarsContainer.tsx
  • extensions/cornerstone/src/components/ViewportWindowLevel/ViewportWindowLevel.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/Colorbar.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/Colormap.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeLighting.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingOptions.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresets.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresetsContent.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingQuality.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShade.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShift.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevel.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevelActionMenu.tsx
  • extensions/cornerstone/src/hooks/useViewportRendering.tsx
  • extensions/cornerstone/src/panels/PanelSegmentation.tsx
  • extensions/cornerstone/src/utils/ActiveViewportBehavior.tsx
  • extensions/default/package.json
  • extensions/default/src/Components/DataSourceConfigurationComponent.tsx
  • extensions/default/src/Components/DataSourceConfigurationModalComponent.tsx
  • extensions/default/src/Components/ItemListComponent.tsx
  • extensions/default/src/Components/ProgressDropdownWithService.tsx
  • extensions/default/src/DicomTagBrowser/DicomTagTable.tsx
  • extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx
  • extensions/default/src/ViewerLayout/index.tsx
  • extensions/default/src/customizations/workListCustomization.ts
  • extensions/dicom-microscopy/package.json
  • extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
  • extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
  • extensions/dicom-microscopy/src/index.tsx
  • extensions/dicom-pdf/package.json
  • extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx
  • extensions/dicom-video/package.json
  • extensions/measurement-tracking/package.json
  • extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx
  • extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx
  • extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx
  • extensions/test-extension/package.json
  • extensions/tmtv/package.json
  • extensions/tmtv/src/Panels/PanelPetSUV.tsx
  • extensions/usAnnotation/package.json
  • modes/basic-dev-mode/package.json
  • modes/basic-test-mode/package.json
  • modes/basic/package.json
  • modes/longitudinal/package.json
  • modes/microscopy/package.json
  • modes/preclinical-4d/package.json
  • modes/segmentation/package.json
  • modes/tmtv/package.json
  • modes/usAnnotation/package.json
  • package.json
  • platform/app/package.json
  • platform/app/src/App.tsx
  • platform/app/src/hooks/useStudyListStateSync.ts
  • platform/app/src/routes/CallbackPage.tsx
  • platform/app/src/routes/DataSourceWrapper.tsx
  • platform/app/src/routes/LegacyWorkList/LegacyWorkList.tsx
  • platform/app/src/routes/LegacyWorkList/filtersMeta.js
  • platform/app/src/routes/LegacyWorkList/index.js
  • platform/app/src/routes/Local/Local.tsx
  • platform/app/src/routes/Mode/Compose.tsx
  • platform/app/src/routes/Mode/Mode.tsx
  • platform/app/src/routes/NotFound/NotFound.tsx
  • platform/app/src/routes/SignoutCallbackComponent.tsx
  • platform/app/src/routes/index.tsx
  • platform/app/src/state/appConfig.tsx
  • platform/app/src/utils/preserveQueryParameters.test.ts
  • platform/core/package.json
  • platform/core/src/hooks/index.ts
  • platform/core/src/hooks/useActiveViewportDisplaySets.ts
  • platform/core/src/hooks/useCustomization.ts
  • platform/core/src/hooks/useRunCommand.tsx
  • platform/docs/package.json
  • platform/docs/src/pages/components/_layout/CodeBlock.tsx
  • platform/docs/src/pages/components/_layout/TableOfContents.tsx
  • platform/docs/src/theme/Footer/index.tsx
  • platform/i18n/package.json
  • platform/ui-next/babel.config.js
  • platform/ui-next/package.json
  • platform/ui-next/src/components/Accordion/Accordion.tsx
  • platform/ui-next/src/components/AllInOneMenu/IconMenu.tsx
  • platform/ui-next/src/components/AllInOneMenu/Item.tsx
  • platform/ui-next/src/components/AllInOneMenu/SubMenu.tsx
  • platform/ui-next/src/components/Button/Button.tsx
  • platform/ui-next/src/components/Calendar/Calendar.tsx
  • platform/ui-next/src/components/Card/Card.tsx
  • platform/ui-next/src/components/Checkbox/Checkbox.tsx
  • platform/ui-next/src/components/CinePlayer/CinePlayer.tsx
  • platform/ui-next/src/components/Command/Command.tsx
  • platform/ui-next/src/components/ContextMenu/ContextMenu.tsx
  • platform/ui-next/src/components/DataRow/DataRow.tsx
  • platform/ui-next/src/components/DataTable/ActionOverlayCell.tsx
  • platform/ui-next/src/components/DataTable/DataTable.tsx
  • platform/ui-next/src/components/DataTable/useResponsiveColumns.tsx
  • platform/ui-next/src/components/Dialog/Dialog.tsx
  • platform/ui-next/src/components/Dialog/useDraggable.ts
  • platform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx
  • platform/ui-next/src/components/DoubleSlider/DoubleSlider.tsx
  • platform/ui-next/src/components/DropdownMenu/DropdownMenu.tsx
  • platform/ui-next/src/components/HoverCard/HoverCard.tsx
  • platform/ui-next/src/components/Input/Input.tsx
  • platform/ui-next/src/components/InputFilter/InputFilter.tsx
  • platform/ui-next/src/components/InputMultiSelect/InputMultiSelect.tsx
  • platform/ui-next/src/components/InputNumber/InputNumber.tsx
  • platform/ui-next/src/components/InvestigationalUseDialog/InvestigationalUseDialog.tsx
  • platform/ui-next/src/components/Label/Label.tsx
  • platform/ui-next/src/components/LayoutSelector/LayoutSelector.tsx
  • platform/ui-next/src/components/LineChart/LineChart.tsx
  • platform/ui-next/src/components/LoadingIndicatorTotalPercent/LoadingIndicatorTotalPercent.tsx
  • platform/ui-next/src/components/NavBar/NavBar.tsx
  • platform/ui-next/src/components/OHIFDialogs/InputDialog.tsx
  • platform/ui-next/src/components/OHIFModals/UserPreferencesModal.tsx
  • platform/ui-next/src/components/Popover/Popover.tsx
  • platform/ui-next/src/components/ProgressDropdown/ProgressDiscreteBar.tsx
  • platform/ui-next/src/components/ProgressDropdown/ProgressDropdown.tsx
  • platform/ui-next/src/components/ProgressDropdown/ProgressItem.tsx
  • platform/ui-next/src/components/ProgressDropdown/ProgressItemDetail.tsx
  • platform/ui-next/src/components/ProgressDropdown/types.ts
  • platform/ui-next/src/components/ProgressLoadingBar/ProgressLoadingBar.tsx
  • platform/ui-next/src/components/ScrollArea/ScrollArea.tsx
  • platform/ui-next/src/components/SegmentationTable/SegmentStatistics.tsx
  • platform/ui-next/src/components/Select/Select.tsx
  • platform/ui-next/src/components/Separator/Separator.tsx
  • platform/ui-next/src/components/Slider/Slider.tsx
  • platform/ui-next/src/components/StudyBrowser/StudyBrowser.tsx
  • platform/ui-next/src/components/StudyItem/StudyItem.tsx
  • platform/ui-next/src/components/StudyList/components/Layout.tsx
  • platform/ui-next/src/components/StudyList/components/PreviewPatientSummary.tsx
  • platform/ui-next/src/components/StudyList/components/Table.tsx
  • platform/ui-next/src/components/Switch/Switch.tsx
  • platform/ui-next/src/components/Table/Table.tsx
  • platform/ui-next/src/components/Tabs/Tabs.tsx
  • platform/ui-next/src/components/Thumbnail/Thumbnail.tsx
  • platform/ui-next/src/components/ThumbnailList/ThumbnailList.tsx
  • platform/ui-next/src/components/Toggle/Toggle.tsx
  • platform/ui-next/src/components/ToggleGroup/ToggleGroup.tsx
  • platform/ui-next/src/components/ToolButton/ToolButtonList.tsx
  • platform/ui-next/src/components/Tooltip/Tooltip.tsx
  • platform/ui-next/src/components/Viewport/PatientInfo.tsx
  • platform/ui-next/src/components/Viewport/ViewportActionArrows.tsx
  • platform/ui-next/src/components/Viewport/ViewportActionBar.tsx
  • platform/ui-next/src/components/Viewport/ViewportActionButton.tsx
  • platform/ui-next/src/components/Viewport/ViewportActionCorners.tsx
  • platform/ui-next/src/components/Viewport/ViewportGrid.tsx
  • platform/ui-next/src/components/Viewport/ViewportOverlay.tsx
  • platform/ui-next/src/components/Viewport/ViewportPane.tsx
  • platform/ui-next/src/contextProviders/CineProvider.tsx
  • platform/ui-next/src/contextProviders/DialogProvider.tsx
  • platform/ui-next/src/contextProviders/DragAndDropProvider.tsx
  • platform/ui-next/src/contextProviders/ImageViewerProvider.tsx
  • platform/ui-next/src/contextProviders/ManagedDialog.tsx
  • platform/ui-next/src/contextProviders/NotificationProvider.tsx
  • platform/ui-next/src/contextProviders/UserAuthenticationProvider.tsx
  • platform/ui-next/src/contextProviders/ViewportDialogProvider.tsx
  • platform/ui-next/src/contextProviders/ViewportGridProvider.tsx
  • platform/ui-next/src/hooks/useDynamicMaxHeight.ts
  • platform/ui/package.json
  • platform/ui/src/components/InputFilterText/InputFilterText.tsx
  • platform/ui/src/components/Tooltip/PortalTooltip.tsx
  • pnpm-workspace.yaml
  • rsbuild.config.ts
  • scripts/reactCompilerLintBudget.mjs
  • tsconfig.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

Comment on lines 136 to +140
// New function to handle image volume loading completion
const handleImageVolumeLoadingCompleted = useCallback(() => {
const handleImageVolumeLoadingCompleted = () => {
setIsLoading(false);
updateViewportHistograms();
}, [updateViewportHistograms]);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 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.

Comment on lines +116 to +117
const listRef = useRef(undefined);
const canvasRef = useRef(undefined);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 || true

Repository: 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])
PY

Repository: 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 || true

Repository: OHIF/Viewers

Length of output: 403


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '220,360p' extensions/default/src/DicomTagBrowser/DicomTagTable.tsx

Repository: 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}')
PY

Repository: 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
done

Repository: 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.

Comment on lines +31 to 42
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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Comment on lines +263 to 270
<div
ref={ref}
{...props}
data-cy="input-dialog-save-button"
onClick={() => onClick(value)}
>
<FooterAction.Primary
onClick={() => onClick(value)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
<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.

Comment thread platform/ui-next/src/contextProviders/ManagedDialog.tsx
Comment on lines +43 to +46
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.`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
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.

@sedghi
sedghi temporarily deployed to fork-pr-approval July 18, 2026 14:41 — with GitHub Actions Inactive
@sedghi
sedghi temporarily deployed to fork-pr-approval July 18, 2026 15:01 — with GitHub Actions Inactive
@sedghi
sedghi temporarily deployed to fork-pr-approval July 18, 2026 15:06 — with GitHub Actions Inactive
@sedghi
sedghi had a problem deploying to fork-pr-approval July 18, 2026 15:53 — with GitHub Actions Failure
@sedghi
sedghi had a problem deploying to fork-pr-approval July 18, 2026 15:58 — with GitHub Actions Failure
@sedghi
sedghi temporarily deployed to fork-pr-approval July 19, 2026 01:39 — with GitHub Actions Inactive
sedghi added 14 commits July 20, 2026 12:39
…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).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Avoid returning unstable nested components.

The getRowComponent function generates and returns a new RowList component function every time it is called during render. When passed as a child to react-window's List, 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-window itemData prop instead.

⚡ Proposed fix using `itemData`

Replace getRowComponent with 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 List component, provide itemData and pass RowList as 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8a96bbc and ee890c9.

⛔ Files ignored due to path filters (5)
  • .webpack/webpack.base.js is excluded by !**/.webpack/**
  • platform/app/.webpack/InjectServiceWorkerManifestPlugin.js is excluded by !**/.webpack/**
  • platform/app/.webpack/webpack.pwa.js is excluded by !**/.webpack/**
  • platform/ui-next/31fb9346313fc3740d7b.woff2 is excluded by !**/*.woff2
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (194)
  • .circleci/config.yml
  • .gitignore
  • .react-compiler-lint-budget.json
  • babel.config.js
  • eslint.config.mjs
  • extensions/cornerstone-dicom-pmap/package.json
  • extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx
  • extensions/cornerstone-dicom-rt/package.json
  • extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx
  • extensions/cornerstone-dicom-seg/package.json
  • extensions/cornerstone-dicom-sr/package.json
  • extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContainer.tsx
  • extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContentItem.tsx
  • extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx
  • extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRTextViewport.tsx
  • extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRViewport.tsx
  • extensions/cornerstone-dynamic-volume/package.json
  • extensions/cornerstone/package.json
  • extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
  • extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx
  • extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx
  • extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx
  • extensions/cornerstone/src/Viewport/Overlays/ViewportSliceProgressScrollbar/ViewportSliceProgressScrollbar.tsx
  • extensions/cornerstone/src/components/ActiveViewportWindowLevel/ActiveViewportWindowLevel.tsx
  • extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx
  • extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx
  • extensions/cornerstone/src/components/DicomUpload/DicomUploadProgressItem.tsx
  • extensions/cornerstone/src/components/NavigationComponent/NavigationComponent.tsx
  • extensions/cornerstone/src/components/SegmentationUtilityButton.tsx
  • extensions/cornerstone/src/components/SelectItemWithModality.tsx
  • extensions/cornerstone/src/components/ViewportColorbar/ViewportColorbar.tsx
  • extensions/cornerstone/src/components/ViewportColorbar/ViewportColorbarsContainer.tsx
  • extensions/cornerstone/src/components/ViewportWindowLevel/ViewportWindowLevel.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/Colorbar.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/Colormap.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeLighting.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingOptions.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresets.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresetsContent.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingQuality.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShade.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShift.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevel.tsx
  • extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevelActionMenu.tsx
  • extensions/cornerstone/src/hooks/useViewportRendering.tsx
  • extensions/cornerstone/src/panels/PanelSegmentation.tsx
  • extensions/cornerstone/src/utils/ActiveViewportBehavior.tsx
  • extensions/default/package.json
  • extensions/default/src/Components/DataSourceConfigurationComponent.tsx
  • extensions/default/src/Components/DataSourceConfigurationModalComponent.tsx
  • extensions/default/src/Components/ItemListComponent.tsx
  • extensions/default/src/Components/ProgressDropdownWithService.tsx
  • extensions/default/src/DicomTagBrowser/DicomTagTable.tsx
  • extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx
  • extensions/default/src/ViewerLayout/index.tsx
  • extensions/default/src/customizations/workListCustomization.ts
  • extensions/dicom-microscopy/package.json
  • extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
  • extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
  • extensions/dicom-microscopy/src/index.tsx
  • extensions/dicom-pdf/package.json
  • extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx
  • extensions/dicom-video/package.json
  • extensions/measurement-tracking/package.json
  • extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx
  • extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx
  • extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx
  • extensions/test-extension/package.json
  • extensions/tmtv/package.json
  • extensions/tmtv/src/Panels/PanelPetSUV.tsx
  • extensions/usAnnotation/package.json
  • modes/basic-dev-mode/package.json
  • modes/basic-test-mode/package.json
  • modes/basic/package.json
  • modes/longitudinal/package.json
  • modes/microscopy/package.json
  • modes/preclinical-4d/package.json
  • modes/segmentation/package.json
  • modes/tmtv/package.json
  • modes/usAnnotation/package.json
  • package.json
  • platform/app/package.json
  • platform/app/src/App.tsx
  • platform/app/src/hooks/useStudyListStateSync.ts
  • platform/app/src/hooks/useWorkListToolbarActions.tsx
  • platform/app/src/routes/CallbackPage.tsx
  • platform/app/src/routes/DataSourceWrapper.tsx
  • platform/app/src/routes/LegacyWorkList/LegacyWorkList.tsx
  • platform/app/src/routes/LegacyWorkList/filtersMeta.js
  • platform/app/src/routes/LegacyWorkList/index.js
  • platform/app/src/routes/Local/Local.tsx
  • platform/app/src/routes/Mode/Compose.tsx
  • platform/app/src/routes/Mode/Mode.tsx
  • platform/app/src/routes/NotFound/NotFound.tsx
  • platform/app/src/routes/SignoutCallbackComponent.tsx
  • platform/app/src/routes/index.tsx
  • platform/app/src/state/appConfig.tsx
  • platform/app/src/utils/preserveQueryParameters.test.ts
  • platform/core/package.json
  • platform/core/src/hooks/index.ts
  • platform/core/src/hooks/useActiveViewportDisplaySets.ts
  • platform/core/src/hooks/useCustomization.ts
  • platform/core/src/hooks/useRunCommand.tsx
  • platform/docs/package.json
  • platform/docs/src/pages/components/_layout/CodeBlock.tsx
  • platform/docs/src/pages/components/_layout/TableOfContents.tsx
  • platform/docs/src/theme/Footer/index.tsx
  • platform/i18n/package.json
  • platform/ui-next/babel.config.js
  • platform/ui-next/package.json
  • platform/ui-next/src/components/Accordion/Accordion.tsx
  • platform/ui-next/src/components/AllInOneMenu/IconMenu.tsx
  • platform/ui-next/src/components/AllInOneMenu/Item.tsx
  • platform/ui-next/src/components/AllInOneMenu/SubMenu.tsx
  • platform/ui-next/src/components/Button/Button.tsx
  • platform/ui-next/src/components/Calendar/Calendar.tsx
  • platform/ui-next/src/components/Card/Card.tsx
  • platform/ui-next/src/components/Checkbox/Checkbox.tsx
  • platform/ui-next/src/components/CinePlayer/CinePlayer.tsx
  • platform/ui-next/src/components/Command/Command.tsx
  • platform/ui-next/src/components/ContextMenu/ContextMenu.tsx
  • platform/ui-next/src/components/DataRow/DataRow.tsx
  • platform/ui-next/src/components/DataTable/ActionOverlayCell.tsx
  • platform/ui-next/src/components/DataTable/DataTable.tsx
  • platform/ui-next/src/components/DataTable/useResponsiveColumns.tsx
  • platform/ui-next/src/components/Dialog/Dialog.tsx
  • platform/ui-next/src/components/Dialog/useDraggable.ts
  • platform/ui-next/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx
  • platform/ui-next/src/components/DoubleSlider/DoubleSlider.tsx
  • platform/ui-next/src/components/DropdownMenu/DropdownMenu.tsx
  • platform/ui-next/src/components/HoverCard/HoverCard.tsx
  • platform/ui-next/src/components/Input/Input.tsx
  • platform/ui-next/src/components/InputFilter/InputFilter.tsx
  • platform/ui-next/src/components/InputMultiSelect/InputMultiSelect.tsx
  • platform/ui-next/src/components/InputNumber/InputNumber.tsx
  • platform/ui-next/src/components/InvestigationalUseDialog/InvestigationalUseDialog.tsx
  • platform/ui-next/src/components/Label/Label.tsx
  • platform/ui-next/src/components/LayoutSelector/LayoutSelector.tsx
  • platform/ui-next/src/components/LineChart/LineChart.tsx
  • platform/ui-next/src/components/LoadingIndicatorTotalPercent/LoadingIndicatorTotalPercent.tsx
  • platform/ui-next/src/components/NavBar/NavBar.tsx
  • platform/ui-next/src/components/OHIFDialogs/InputDialog.tsx
  • platform/ui-next/src/components/OHIFModals/UserPreferencesModal.tsx
  • platform/ui-next/src/components/Popover/Popover.tsx
  • platform/ui-next/src/components/ProgressDropdown/ProgressDiscreteBar.tsx
  • platform/ui-next/src/components/ProgressDropdown/ProgressDropdown.tsx
  • platform/ui-next/src/components/ProgressDropdown/ProgressItem.tsx
  • platform/ui-next/src/components/ProgressDropdown/ProgressItemDetail.tsx
  • platform/ui-next/src/components/ProgressDropdown/types.ts
  • platform/ui-next/src/components/ProgressLoadingBar/ProgressLoadingBar.tsx
  • platform/ui-next/src/components/ScrollArea/ScrollArea.tsx
  • platform/ui-next/src/components/SegmentationTable/SegmentStatistics.tsx
  • platform/ui-next/src/components/Select/Select.tsx
  • platform/ui-next/src/components/Separator/Separator.tsx
  • platform/ui-next/src/components/Slider/Slider.tsx
  • platform/ui-next/src/components/StudyBrowser/StudyBrowser.tsx
  • platform/ui-next/src/components/StudyItem/StudyItem.tsx
  • platform/ui-next/src/components/StudyList/components/Layout.tsx
  • platform/ui-next/src/components/StudyList/components/PreviewPatientSummary.tsx
  • platform/ui-next/src/components/StudyList/components/Table.tsx
  • platform/ui-next/src/components/Switch/Switch.tsx
  • platform/ui-next/src/components/Table/Table.tsx
  • platform/ui-next/src/components/Tabs/Tabs.tsx
  • platform/ui-next/src/components/Thumbnail/Thumbnail.tsx
  • platform/ui-next/src/components/ThumbnailList/ThumbnailList.tsx
  • platform/ui-next/src/components/Toggle/Toggle.tsx
  • platform/ui-next/src/components/ToggleGroup/ToggleGroup.tsx
  • platform/ui-next/src/components/ToolButton/ToolButtonList.tsx
  • platform/ui-next/src/components/Tooltip/Tooltip.tsx
  • platform/ui-next/src/components/Viewport/PatientInfo.tsx
  • platform/ui-next/src/components/Viewport/ViewportActionArrows.tsx
  • platform/ui-next/src/components/Viewport/ViewportActionBar.tsx
  • platform/ui-next/src/components/Viewport/ViewportActionButton.tsx
  • platform/ui-next/src/components/Viewport/ViewportActionCorners.tsx
  • platform/ui-next/src/components/Viewport/ViewportGrid.tsx
  • platform/ui-next/src/components/Viewport/ViewportOverlay.tsx
  • platform/ui-next/src/components/Viewport/ViewportPane.tsx
  • platform/ui-next/src/contextProviders/CineProvider.tsx
  • platform/ui-next/src/contextProviders/DialogProvider.tsx
  • platform/ui-next/src/contextProviders/DragAndDropProvider.tsx
  • platform/ui-next/src/contextProviders/ImageViewerProvider.tsx
  • platform/ui-next/src/contextProviders/ManagedDialog.tsx
  • platform/ui-next/src/contextProviders/NotificationProvider.tsx
  • platform/ui-next/src/contextProviders/UserAuthenticationProvider.tsx
  • platform/ui-next/src/contextProviders/ViewportDialogProvider.tsx
  • platform/ui-next/src/contextProviders/ViewportGridProvider.tsx
  • platform/ui-next/src/hooks/useDynamicMaxHeight.ts
  • platform/ui/package.json
  • platform/ui/src/components/InputFilterText/InputFilterText.tsx
  • platform/ui/src/components/Tooltip/PortalTooltip.tsx
  • pnpm-workspace.yaml
  • rsbuild.config.ts
  • scripts/reactCompilerLintBudget.mjs
  • tsconfig.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

Comment thread extensions/tmtv/package.json
@coderabbitai coderabbitai Bot mentioned this pull request Jul 22, 2026
6 tasks
"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",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 wayfarer3130 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@jbocce
jbocce self-requested a review August 4, 2026 17:07
"keywords": []
"keywords": [],
"peerDependencies": {
"react": "^19.0.0",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants