Skip to content

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

Open
sedghi wants to merge 26 commits into
masterfrom
ohifReact
Open

feat: React 19 + React Compiler, compiler-era cleanup, and rsbuild production build#6164
sedghi wants to merge 26 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.2.7) instead of dependencies. External consumers of the published package must be on React 19.2.7 or newer.
  • 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).

Breaking changes

React 19 is required for consumers of OHIF's published packages. @ohif/ui-next moves react/react-dom from dependencies to peerDependencies: ^19.2.7, so the consuming application supplies React rather than receiving OHIF's own copy — which is what keeps a single React instance on the page. Every extension and mode declares React 19.2.7 as a peer as well, so third-party extensions built against React 18 need upgrading.

There is also a runtime-only edge worth calling out: UMD builds that externalize React read useMemoCache off the host's React, and that API exists only in React 19. A React 18 host fails when a compiled component first renders, with no install-time warning to catch it.

LegacyWorkList and the workList.variant customization are removed. The ui-next WorkList is now always mounted at /. Anyone who set workList.variant: 'legacy' as a 3.13 migration opt-out loses it — and because OHIF ignores unrecognized customization ids silently, the setting stops working with no error surfaced. @ohif/ui is dropped from the 11 packages that still carried it, as LegacyWorkList was its last in-app consumer; the package itself still builds and publishes.

SmartScrollbarFill and SmartScrollbarEndpoints now require marked to change identity. The old contract — mutate the array in place and bump a version prop — cannot work under the React Compiler, which memoizes both components on marked and drops a token it never reads. That left the viewport scrollbar's loading fill frozen until an unrelated resize. version is removed from both components and from ByteArrayHandle, so passing it is a compile error rather than a silent no-op. useByteArray() publishes a new view of the same buffer on every change, so consumers of the hook need no changes.

All three are documented in a new 3.13 → 3.14 migration guide added by this PR: platform/docs/docs/migration-guide/3p13-to-3p14/ (react-19.md, work-list.md, smart-scrollbar.md).

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.
  • The UMD package builds compile too, so the published bytes match what the app build produces. REACT_COMPILER=off remains a documented manual kill switch for debugging a suspected miscompile; nothing in the repo sets it. Both packaging shapes were measured. platform/ui-next (externalizes React) grows +3.2%, all of it memo-cache scaffolding: react/compiler-runtime inlines as three lines reading useMemoCache off the host's React, with no React version string, dispatcher, or error text anywhere in the output. extensions/cornerstone-dicom-sr (bundles React) grows +1,088 B, with React bundled exactly once either way. The compiler introduces no second copy of React in either shape.
  • One consequence worth stating explicitly: the shim reaches React.__CLIENT_INTERNALS….useMemoCache, which React 18 does not have. A compiled UMD that externalizes React therefore requires the host to supply React 19 specifically — a runtime-contract tightening that main-field consumers hit at runtime rather than install time. @ohif/ui-next declares peerDependencies: ^19.2.7 accordingly.
  • 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. Two gates, deliberately different: the React 19 guardrails (no-restricted-imports/-properties/-syntax) fail on any hit and are never budgeted, so a new forwardRef can't hide inside unused headroom; the pre-existing react-hooks debt is budgeted and may only go down (currently 174 errors / 133 warnings). Diagnostics for rules this minimal config doesn't load are itemized and excluded — they come from eslint-disable comments aimed at the main .eslintrc, and counting them made a master merge fail the ratchet.

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.
  • Rollback path. If the production bundle regresses, build:legacy still produces the previous rspack output with React 19 untouched — no revert needed, though CI never runs rspack build, so those paths are unverified. React 19 has no equivalent fallback: a regression there means reverting this PR, and the bundler work with it.
  • 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: opt-outs

The compiler is enabled workspace-wide. Two opt-out mechanisms exist, deliberately different, used in the following places:

  • Per-file 'use no memo' directives cover the eight components under
    extensions/cornerstone/src/Viewport/. They 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. This was bisected to the compiler-enablement commit with a deterministic e2e
    oracle and verified fixed. The directive sits next to the code and the reason for it.

  • platform/ui-next/src/components/DataTable/ (seven files) is opted out for a different reason: TanStack Table exposes its state through methods on objects whose identity is deliberately stable across renders (table.getState(), column.getIsSorted()), so the compiler caches those calls and every value derived from them freezes at mount. That was the single root cause behind the reported study-list defects — a page number that never changed, a sort that would not reverse, and View-menu toggles with no effect. The compiler already reports Compilation Skipped: Use of incompatible library where useReactTable is called, but that verdict does not travel with the table object into the components that receive it through context, so these opt-outs are permanent rather than temporary cleanup.

  • One path exclude, for frozen platform/ui: exclude: [/[\\/]platform[\\/]ui[\\/]/] in
    babel.config.js, mirrored by the (?!ui[\\/]) clause in rsbuild.config.ts's
    REACT_COMPILER_INCLUDE. Both patterns require a separator after ui, so
    platform/ui-next compiles normally - apart from the DataTable directives above.
    platform/ui stays out on two grounds: it sits outside the
    compiler-lint scope, so its rules-of-React violations were never counted in the budget;
    and nothing exercises it now that the LegacyWorkList route is deleted, so a silent
    miscompile would ship unnoticed.

  • Per-file opt-outs stay directives rather than joining the path list because a per-file path
    list has to be mirrored in both configs and drifts silently. One entry for one frozen
    package does not.

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 (16 packages) and build:package-all-1 (11) both exit 0 with the compiler active on every package - 22 compile cleanly, 5 with warnings, and those warnings are only the pre-existing asset/entrypoint size ones. Build time rises with the compiler on (ui-next 22s -> 48s locally), so BUILD_PACKAGES wall time is worth watching in CI.
  • 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 ~174 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 1814ad1
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6a84b02c97a15700087c8109
😎 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 detailed and relevant, but it omits the required Checklist and Tested Environment sections from the repository template. Add the required checklist with applicable boxes marked, and include the tested OS, Node version, and browser.
✅ 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 clearly summarizes the PR's primary React 19, React Compiler, and rsbuild changes and follows the semantic-release format.
✨ 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 #6638

Run Properties:  status check passed Passed #6638  •  git commit 1814ad137f: Ignore GHSA-ggr8-5vv4-36mx in pnpm-workspace.yaml.
Project Viewers
Branch Review ohifReact
Run status status check passed Passed #6638
Run duration 01m 52s
Commit git commit 1814ad137f: Ignore GHSA-ggr8-5vv4-36mx in pnpm-workspace.yaml.
Committer Joe Boccanfuso
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

@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
Comment thread platform/ui-next/package.json Outdated
@dan-rukas

Copy link
Copy Markdown
Member
smart-scroll-resize.mp4

Issue: Scrollbar loading progress freezes during series load and only shows feedback after resizing the window

Environment: any stack viewport with a visible loading fill should show it.

Steps to reproduce:

  1. Open a multi-slice study in the viewer with a cold cache.
  2. Watch the viewport scrollbar (SmartScrollbar) while the series loads, and scroll through slices during loading.
  3. Resize the browser window mid-load.
  4. Continue scrolling after the resize.

Observed (video above):

  1. The loading fill renders only a few early segments, then stops updating.
  2. While reading, the position indicator moves but neither the loaded fill nor the viewed fill updates.
  3. Resizing the window snaps the fill up to date — once.
  4. After the resize, the freeze resumes.

Expected (current master behavior): the loaded fill grows in real time as slices arrive, and the viewed fill marks dwelled slices.

@dan-rukas

Copy link
Copy Markdown
Member
study-list-searching-large-rows.mp4

Issue: Study list filter rows render oversized

Steps to reproduce:

  1. Open the study list — rows render at the normal height.
  2. Type into the patient-name filter field.
  3. Observe the filtered result rows.

Observed (video above): after typing in the name filter, the result rows render at a visibly larger height than the initial rows.

Expected: filtered rows render at the same height as the unfiltered list.

@dan-rukas

dan-rukas commented Aug 5, 2026

Copy link
Copy Markdown
Member
responsive-column-behavior-missing.mp4

Issue: Study list responsive column behavior doesn't work

Steps to reproduce:

  1. Open the study list.
  2. Resize the browser window through the widths where columns should adapt (hide/show/reflow).

Observed (video above): the responsive column behavior doesn't happen — columns fail to adapt as the available width changes.

Expected: the study list's columns adapt to available width (the useResponsiveColumns behavior in the ui-next DataTable stack).

jbocce added 2 commits August 12, 2026 16:06
Removes REACT_COMPILER=off from all 27 package build scripts. Building
platform/ui-next (which externalizes React) and extensions/cornerstone-dicom-sr
(which bundles it) both ways shows react/compiler-runtime adds only a three-line
shim that binds to the React already in play, so the flag was guarding against a
duplicate copy that never materializes — while leaving the published packages as
the only artifacts shipping this PR's ~100 removed useMemo/useCallback with
nothing to restore them. The one real exclusion, frozen platform/ui, moves into
babel.config.js so the policy lives with the transform rather than in 50
copy-pasted script prefixes.
@jbocce
jbocce deployed to fork-pr-approval August 12, 2026 20:21 — with GitHub Actions Active
jbocce added 2 commits August 13, 2026 12:11
The budget summed every diagnostic into one number, so a new forwardRef could
hide inside unrelated headroom, and eslint-disable comments aimed at the legacy
.eslintrc config counted as errors — which is how a master merge tripped the
ratchet. Guardrail rules now fail on any hit regardless of budget, unresolvable
rule names are itemized rather than counted, and the budget tightens to 174/133.
@jbocce
jbocce deployed to fork-pr-approval August 13, 2026 18:26 — with GitHub Actions Active
chore(deps): pin remaining version ranges to lockfile-resolved versions
@jbocce
jbocce deployed to fork-pr-approval August 14, 2026 12:42 — with GitHub Actions Active
@hongxiao-gif

Copy link
Copy Markdown
Collaborator

I found a couple of issues with the study list:

  1. Study list row height increases after filtering based on the number of remaining rows
  2. After a column is sorted in ascending order, it cannot be sorted again in descending order.
  3. The month and year dropdowns have a white background, making the month and year text difficult to see
  4. The page number is incorrect, and the user cannot select a different number of studies to display per page.
  5. The user is unable to deselect columns from the display settings.
  6. When the page is resized, the columns on the right side become hidden.
    TEST-93_Viewer Sanity Check_StudyList.docx

… and update the useByteArray hook to use a new identity on every change.
@jbocce
jbocce deployed to fork-pr-approval August 17, 2026 16:51 — with GitHub Actions Active
@jbocce

jbocce commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

smart-scroll-resize.mp4

Issue: Scrollbar loading progress freezes during series load and only shows feedback after resizing the window

Environment: any stack viewport with a visible loading fill should show it.

Steps to reproduce:

  1. Open a multi-slice study in the viewer with a cold cache.
  2. Watch the viewport scrollbar (SmartScrollbar) while the series loads, and scroll through slices during loading.
  3. Resize the browser window mid-load.
  4. Continue scrolling after the resize.

Observed (video above):

  1. The loading fill renders only a few early segments, then stops updating.
  2. While reading, the position indicator moves but neither the loaded fill nor the viewed fill updates.
  3. Resizing the window snaps the fill up to date — once.
  4. After the resize, the freeze resumes.

Expected (current master behavior): the loaded fill grows in real time as slices arrive, and the viewed fill marks dwelled slices.

Thanks Dan. I think I fixed this with commit 90ea52e

@jbocce
jbocce deployed to fork-pr-approval August 17, 2026 17:53 — with GitHub Actions Active
@jbocce

jbocce commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

The month and year dropdowns are native <select> elements rendered invisibly
beneath the styled caption, and browsers paint their option popups from the
select's own colors rather than inherited ones. Without them the popup fell back
to a light browser default, leaving the theme's near-white text unreadable.
Pre-existing since #6005 and present in 3.13 — worth considering for backport.
@jbocce
jbocce deployed to fork-pr-approval August 18, 2026 00:11 — with GitHub Actions Active
@jbocce

jbocce commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

One thing worth flagging before merge: @xstate/react@3.2.2 in extensions/measurement-tracking declares react: ^16.8.0 || ^17.0.0 || ^18.0.0 and is silently coerced to React 19 by the workspace overrides, with strict-peer-dependencies=false turning the violation into a warning nobody sees. It's the only coerced package in live application code — everything else in that position is stranded in frozen @ohif/ui — and it's used in TrackedMeasurementsContext, so it sits on the measurement-tracking workflow.

I audited it rather than guess. It uses none of what React 19 removed (no findDOMNode, defaultProps, runtime propTypes, ReactDOM.render, or string refs) and stays entirely on stable hooks, and the tracking workflow passes both Cypress and Playwright. So the practical risk looks low.

Closing the gap properly isn't cheap. React 19 support doesn't arrive until @xstate/react@5, and every version from 4 onward requires xstate 5 — there's no release that supports React 19 while staying on xstate 4. That makes it an xstate 4 → 5 migration (machine creation API, actions, actor model, typegen removal) in a clinical workflow, which I'd schedule as its own work rather than fold in here.

Raising it so the decision is explicit rather than implicit.

@jbocce
jbocce deployed to fork-pr-approval August 18, 2026 19:19 — with GitHub Actions Active
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.

5 participants