Skip to content

ohifohifupdateplaywightimgaes#6043

Open
sedghi wants to merge 16 commits into
masterfrom
ohifohifupdateplaywightimgaes
Open

ohifohifupdateplaywightimgaes#6043
sedghi wants to merge 16 commits into
masterfrom
ohifohifupdateplaywightimgaes

Conversation

@sedghi
Copy link
Copy Markdown
Member

@sedghi sedghi commented May 26, 2026

  • chore(tests): Update multiple screenshot test images for various specs
  • feat(screenshot-reviewer): Add screenshot review tool and update package.json scripts
  • fix(DICOMSRDisplayTool): Improve actor presence check in viewport
  • chore(tests): Update multiple screenshot assets for various specs
  • chore(tests): Integrate waitForPaintToSettle and waitForViewportsRendered in multiple specs for improved rendering stability

Context

Changes & Results

Testing

Checklist

PR

  • [] My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Code

  • [] My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • [] The documentation page has been updated as necessary for any public API
    additions or removals.

Tested Environment

  • [] OS:
  • [] Node version:
  • [] Browser:

Summary by CodeRabbit

  • New Features

    • Interactive screenshot reviewer with web UI and local server (side-by-side, slider, blink, staging, copy controls).
  • Improvements

    • Viewport handling extended to support legacy 3D viewports and updated display-set behavior.
  • Bug Fixes

    • Rendering skipped for viewports with no visible content to avoid overlay noise.
  • Tests

    • Improved stability: render synchronization, paint-settling helper, and updated Jest assertions.
  • Chores

    • Updated test tooling versions and added screenshot review script; package resolution tweaks.

Review Change Stack

Copy link
Copy Markdown

@claude claude Bot left a comment

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 to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 26, 2026

Deploy Preview for ohif-dev ready!

Name Link
🔨 Latest commit 2b38e44
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6a1d81eeedb28100087237af
😎 Deploy Preview https://deploy-preview-6043--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.

@cypress
Copy link
Copy Markdown

cypress Bot commented May 26, 2026

Viewers    Run #6326

Run Properties:  status check failed Failed #6326  •  git commit 2b38e4405c: Resolve upstream merge conflicts
Project Viewers
Branch Review ohifohifupdateplaywightimgaes
Run status status check failed Failed #6326
Run duration 02m 21s
Commit git commit 2b38e4405c: Resolve upstream merge conflicts
Committer Alireza
View all properties for this run ↗︎

Test results
Tests that failed  Failures 2
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 26
View all changes introduced in this branch ↗︎

Tests for review

Failed  HangingProtocol.spec.js • 1 failed test

View Output Video

Test Artifacts
OHIF HP > Should navigate next/previous stage Test Replay Screenshots Video
Failed  OHIFDoubleClick.spec.js • 1 failed test

View Output Video

Test Artifacts
OHIF Double Click > Should double click each viewport to one up and back Test Replay Screenshots Video

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.

Can you switch this to viewports only compare?

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.

ditto

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

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

Upgrades Jest and Babel test config, modernizes Jest matchers, adds paint-settle test utilities and integrates them into screenshot tests, applies small viewport/DOM fixes, loosens some visual-test assertions, and adds a local screenshot-review server + Alpine UI.

Changes

Testing Infrastructure & Compatibility

Layer / File(s) Summary
Jest 30 upgrade and Babel configuration
addOns/externals/devDependencies/package.json, babel.config.js
Jest devDependencies bumped and explicit babel-plugin-istanbul removed from env.test.plugins with explanatory comments.
Jest test matcher modernization
platform/core/src/utils/Queue.test.js, platform/core/src/utils/absoluteUrl.test.js, platform/core/src/utils/addServer.test.js, platform/core/src/utils/hierarchicalListUtils.test.js, platform/core/src/utils/progressTrackingUtils.test.js
Updated test assertions to use standard Jest matchers (toHaveBeenCalledWith, toHaveBeenNthCalledWith, toHaveBeenCalledTimes) and simplified absoluteUrl test environment handling.
Paint-settle utilities
tests/utils/waitForViewportsRendered.ts, tests/utils/index.ts
waitForViewportsRendered and waitForViewportRenderCycle accept a settle option (default true) and export waitForPaintToSettle; barrel export updated.
Screenshot tests: render + paint synchronization
tests/JumpToMeasurementMPR.spec.ts, tests/SEGHydrationFrom3DFourUp.spec.ts, tests/SEGHydrationFromMPR.spec.ts, tests/SEGNoHydrationThenMPR.spec.ts, tests/SRHydration.spec.ts, tests/Scoord3dProbe.spec.ts, tests/ScoordRectangle.spec.ts
Playwright/screenshot tests updated to await viewport render completion and paint settling (replacing fixed timeouts) before taking screenshots.
Visual test tolerance updates
tests/Livewire.spec.ts, tests/Spline.spec.ts
Loosen exact-area assertions by rounding and applying tolerances to account for DPI/resolution and snapping differences.
Viewport & DOM compatibility fixes
extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts, extensions/cornerstone/src/utils/generateSegmentationCSVReport.test.ts, extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts, testdata
Prefer viewport.getActors() when available; tests patch specific jsdom document methods rather than replacing global.document; CornerstoneViewportService updated to use display-sets and treat legacy 3D viewports as volume-like; testdata pointer updated.

Screenshot Reviewer Tool

Layer / File(s) Summary
Screenshot reviewer HTTP server
scripts/screenshot-reviewer.mjs
Adds an HTTP server with /api/list (parses git status --porcelain=v1 -z for PNGs), /api/image (serve HEAD or worktree PNGs), and POST /api/stage (run git add), with validation and request limits.
Screenshot reviewer UI and component
scripts/screenshot-reviewer.html
Adds a single-file Alpine.js UI for listing, filtering, viewing (side/slider/blink), staging, and annotating screenshot changes; persists notes to localStorage.
Reviewer tooling integration
package.json, bunfig.toml, bunfig.update-lockfile.toml
Adds review:screenshots npm script, pins chalk and tmp in resolutions, and sets bun install minimumReleaseAge.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through tests and dusted timeouts clear,
Two RAFs then a timeout — the pixels cheer,
Jest bumped up, matchers tidy and fair,
A reviewer page to stage what’s there,
Sniff, compare, and git-add — carrot treats near!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description includes bullet points summarizing changes and retains template sections, but lacks filled Context, Changes & Results, Testing details, and unchecked checklist items—indicating incomplete required sections. Complete the Context section (link to related issues), detail Changes & Results with before/after info, describe Testing steps, and mark all applicable checklist items with [x].
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'ohifohifupdateplaywightimgaes' is vague, uses non-descriptive terms, and does not clearly convey the main changes (test updates, screenshot reviewer tool, rendering stability fixes). Use a descriptive title following semantic-release format, such as 'chore(tests): Update screenshot tests and add screenshot review tool' or 'chore(tests): Improve rendering stability in E2E tests'.
✅ Passed checks (2 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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ohifohifupdateplaywightimgaes

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
addOns/externals/devDependencies/package.json (1)

55-58: Confirm Jest/jest-junit upgrade versions are published and have no known GitHub-advisory vulnerabilities.

  • jest@30.2.0, jest-environment-jsdom@30.2.0, and jest-junit@16.0.0 are present on npm.
  • GitHub security advisory lookup returned no known vulnerabilities for jest or jest-junit.

Jest is a major bump (29.x → 30.x) and may still require reviewing the Jest 30 upgrade notes and running the test suite.

🤖 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 `@addOns/externals/devDependencies/package.json` around lines 55 - 58, Confirm
the new Jest-related versions are safe and compatible: verify that
"jest@30.2.0", "jest-environment-jsdom@30.2.0", and "jest-junit@16.0.0" are
published to npm, run a GitHub Advisory and npm audit check for any
vulnerabilities, update the lockfile (package-lock.json or yarn.lock) by running
npm ci/install to pin these versions, run the full test suite and address any
Jest 30 compatibility issues (refer to jest config and test files), and if
failures occur, revert to a compatible minor version or apply the necessary Jest
30 migration changes in test setup/configuration.
🤖 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 `@scripts/screenshot-reviewer.html`:
- Around line 379-508: The x-text binding in the notes footer uses selected.name
which throws when selected is null; update the binding to use optional chaining
(selected?.name) so the expression is safe when the element is hidden via
x-show; locate the notes footer block (the element with <span class="count"
x-show="selected" x-text="selected.name">) and change the x-text to use
selected?.name to match other bindings like selected?.path.

In `@scripts/screenshot-reviewer.mjs`:
- Around line 138-159: handleImage currently pipes
createReadStream(absolutePath) to response without handling stream errors;
attach an 'error' listener to the stream returned by createReadStream in the
worktree branch (after resolveScreenshotPath and the version checks) to catch
file-not-found or read errors, log or include the error via your existing
logger, send an appropriate response (e.g., response.writeHead(404 or 500) and
response.end()) and ensure you remove/cleanup listeners as needed so the
response cannot hang or throw an unhandled exception when the file is
unreadable.

---

Nitpick comments:
In `@addOns/externals/devDependencies/package.json`:
- Around line 55-58: Confirm the new Jest-related versions are safe and
compatible: verify that "jest@30.2.0", "jest-environment-jsdom@30.2.0", and
"jest-junit@16.0.0" are published to npm, run a GitHub Advisory and npm audit
check for any vulnerabilities, update the lockfile (package-lock.json or
yarn.lock) by running npm ci/install to pin these versions, run the full test
suite and address any Jest 30 compatibility issues (refer to jest config and
test files), and if failures occur, revert to a compatible minor version or
apply the necessary Jest 30 migration changes in test setup/configuration.
🪄 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: c8047956-ce75-44fd-aed7-6e1aae24829c

📥 Commits

Reviewing files that changed from the base of the PR and between 5ea22fc and 6ac4492.

⛔ Files ignored due to path filters (111)
  • bun.lock is excluded by !**/*.lock
  • tests/screenshots/chromium/3DFourUp.spec.ts/threeDFourUpDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/3DMain.spec.ts/threeDMainDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/3DOnly.spec.ts/threeDOnlyDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/3DPrimary.spec.ts/threeDPrimaryDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/Angle.spec.ts/angleDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly0.png is excluded by !**/*.png
  • tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly1.png is excluded by !**/*.png
  • tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly2.png is excluded by !**/*.png
  • tests/screenshots/chromium/AxialPrimary.spec.ts/axialPrimaryDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/Bidirectional.spec.ts/bidirectionalDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/Circle.spec.ts/circleDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/CobbAngle.spec.ts/cobbangleDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/ContextMenu.spec.ts/contextMenuNearBottomEdgeNotClipped.png is excluded by !**/*.png
  • tests/screenshots/chromium/ContextMenu.spec.ts/preContextMenuNearBottomEdge.png is excluded by !**/*.png
  • tests/screenshots/chromium/DataOverlayMenu.spec.ts/noOverlay.png is excluded by !**/*.png
  • tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlay2d-tta-nnU-Net-Segmentation.png is excluded by !**/*.png
  • tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWith2d-tta-nnU-Net-SegmentationSelected.png is excluded by !**/*.png
  • tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWithSegmentationOverlaysRemoved.png is excluded by !**/*.png
  • tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWithSegmentationSelected.png is excluded by !**/*.png
  • tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlaySegmentation.png is excluded by !**/*.png
  • tests/screenshots/chromium/DicomTagBrowser.spec.ts/dicomTagBrowserDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/DicomTagBrowser.spec.ts/scrollBarRenderedProperly.png is excluded by !**/*.png
  • tests/screenshots/chromium/Ellipse.spec.ts/ellipseDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/FlipHorizontal.spec.ts/flipHorizontalDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/Invert.spec.ts/invertDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-changeSeriesInMPR.png is excluded by !**/*.png
  • tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-initialDraw.png is excluded by !**/*.png
  • tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpInMPR.png is excluded by !**/*.png
  • tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpToMeasurementAfterSeriesChange.png is excluded by !**/*.png
  • tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-jumpToMeasurementStack.png is excluded by !**/*.png
  • tests/screenshots/chromium/JumpToMeasurementMPR.spec.ts/jumpToMeasurementMPR-scrollAway.png is excluded by !**/*.png
  • tests/screenshots/chromium/LabelMapSegLocking.spec.ts/lockedSegPostEdit.png is excluded by !**/*.png
  • tests/screenshots/chromium/LabelMapSegLocking.spec.ts/lockedSegPreEdit.png is excluded by !**/*.png
  • tests/screenshots/chromium/LabelMapSegLocking.spec.ts/unlockedSegPostEdit.png is excluded by !**/*.png
  • tests/screenshots/chromium/LabelMapSegLocking.spec.ts/unlockedSegPreEdit.png is excluded by !**/*.png
  • tests/screenshots/chromium/Length.spec.ts/lengthDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/Livewire.spec.ts/livewireDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/MPR.spec.ts/mprDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/MPRThenRTOverlayNoHydration.spec.ts/mprPostRTOverlayNoHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/MPRThenRTOverlayNoHydration.spec.ts/mprPreRTOverlayNoHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/MPRThenSEGOverlayNoHydration.spec.ts/mprPostSEGOverlayNoHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/MPRThenSEGOverlayNoHydration.spec.ts/mprPreSEGOverlayNoHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/MultipleSegmentationDataOverlays.spec.ts/overlaySEGsAndRTDisplayed.png is excluded by !**/*.png
  • tests/screenshots/chromium/MultipleSegmentationDataOverlays.spec.ts/overlaysDisplayed.png is excluded by !**/*.png
  • tests/screenshots/chromium/MultipleSegmentationDataOverlays.spec.ts/threeSegOverlaysInOverlayMenu.png is excluded by !**/*.png
  • tests/screenshots/chromium/Probe.spec.ts/probeDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayFirstImage.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayMiddleImage.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTDataOverlayNoHydrationThenMPR.spec.ts/rtDataOverlayNoHydrationPostMpr.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTDataOverlayNoHydrationThenMPR.spec.ts/rtDataOverlayNoHydrationPreMpr.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydration.spec.ts/rtJumpToStructure.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydration.spec.ts/rtPostHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydration.spec.ts/rtPreHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydration2.spec.ts/rtPostHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydration2.spec.ts/rtPreHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/firstLoadPostHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/secondLoadPostHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/viewportAfterFirstDelete.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/viewportAfterSecondDelete.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydrationFromMPR.spec.ts/mprAfterRT.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydrationFromMPR.spec.ts/mprAfterRTHydrated.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydrationFromMPR.spec.ts/mprAfterRTHydratedAfterLayoutChange.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydrationFromMPR.spec.ts/mprBeforeRT.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydrationThenMPR.spec.ts/rtPostHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTHydrationThenMPR.spec.ts/rtPostHydrationMPRAxialPrimary.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTNoHydrationThenMPR.spec.ts/rtNoHydrationPostMpr.png is excluded by !**/*.png
  • tests/screenshots/chromium/RTNoHydrationThenMPR.spec.ts/rtNoHydrationPreMpr.png is excluded by !**/*.png
  • tests/screenshots/chromium/Rectangle.spec.ts/rectangleDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/Reset.spec.ts/resetDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/RotateRight.spec.ts/rotateRightDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayFirstImage.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayMiddleImage.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGDataOverlayNoHydrationThenMPR.spec.ts/segDataOverlayNoHydrationPostMpr.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGDataOverlayNoHydrationThenMPR.spec.ts/segDataOverlayNoHydrationPreMpr.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGDrawingToolsResizing.spec.ts/brushTool.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGDrawingToolsResizing.spec.ts/eraserTool.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydration.spec.ts/segPostHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydration.spec.ts/segPreHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationDeleteAndReload.spec.ts/viewportAfterSecondDelete.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationDeleteAndReload.spec.ts/viewportAfterSecondHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationFrom3DFourUp.spec.ts/afterSEGHydrated.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationFrom3DFourUp.spec.ts/backTo3DFourUp.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationFrom3DFourUp.spec.ts/threeDFourUpAfterSEG.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationFrom3DFourUp.spec.ts/threeDFourUpAfterSegHydrated.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationFrom3DFourUp.spec.ts/threeDFourUpBeforeSEG.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationFromMPR.spec.ts/mprAfterSEG.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationFromMPR.spec.ts/mprAfterSegHydrated.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationFromMPR.spec.ts/mprAfterSegHydratedAfterLayoutChange.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationFromMPR.spec.ts/mprBeforeSEG.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationThenMPR.spec.ts/segPostHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGHydrationThenMPR.spec.ts/segPostHydrationMPRAxialPrimary.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGNoHydrationThenMPR.spec.ts/segNoHydrationPostMpr.png is excluded by !**/*.png
  • tests/screenshots/chromium/SEGNoHydrationThenMPR.spec.ts/segNoHydrationPreMpr.png is excluded by !**/*.png
  • tests/screenshots/chromium/SRHydration.spec.ts/srJumpToMeasurement.png is excluded by !**/*.png
  • tests/screenshots/chromium/SRHydration.spec.ts/srPostHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/SRHydration.spec.ts/srPreHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbeDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbeJumpToMeasurement.png is excluded by !**/*.png
  • tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbePostHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbePreHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectangleDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectangleJumpToMeasurement.png is excluded by !**/*.png
  • tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectanglePostHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectanglePreHydration.png is excluded by !**/*.png
  • tests/screenshots/chromium/Spline.spec.ts/splineDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/Worklist.spec.ts/scrollBarRenderedProperly.png is excluded by !**/*.png
  • tests/screenshots/chromium/ZoomIn.spec.ts/magnifyViewportDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/mpr2.spec.ts/mprDisplayedCorrectly.png is excluded by !**/*.png
  • tests/screenshots/chromium/mpr2.spec.ts/mprDisplayedCorrectlyZoomed.png is excluded by !**/*.png
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (22)
  • addOns/externals/devDependencies/package.json
  • babel.config.js
  • extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts
  • extensions/cornerstone/src/utils/generateSegmentationCSVReport.test.ts
  • package.json
  • platform/core/src/utils/Queue.test.js
  • platform/core/src/utils/absoluteUrl.test.js
  • platform/core/src/utils/addServer.test.js
  • platform/core/src/utils/hierarchicalListUtils.test.js
  • platform/core/src/utils/progressTrackingUtils.test.js
  • scripts/screenshot-reviewer.html
  • scripts/screenshot-reviewer.mjs
  • testdata
  • tests/JumpToMeasurementMPR.spec.ts
  • tests/SEGHydrationFrom3DFourUp.spec.ts
  • tests/SEGHydrationFromMPR.spec.ts
  • tests/SEGNoHydrationThenMPR.spec.ts
  • tests/SRHydration.spec.ts
  • tests/Scoord3dProbe.spec.ts
  • tests/ScoordRectangle.spec.ts
  • tests/utils/index.ts
  • tests/utils/waitForViewportsRendered.ts

Comment on lines +379 to +508
<body>
<div class="app" x-data="reviewer()" x-init="init()">
<aside class="sidebar">
<div class="sidebar-header">
<div class="title-row">
<h1>Screenshot Review</h1>
<button class="ghost" @click="refresh()">Refresh</button>
</div>
<input class="search" type="search" placeholder="Filter" x-model="query" />
<label class="check-row">
<input type="checkbox" x-model="hideStaged" />
<span>Hide staged</span>
</label>
<div class="count" x-text="`${filteredEntries.length} changed screenshots`"></div>
</div>
<div class="list">
<div class="section-title" x-show="unstagedEntries.length">Unstaged</div>
<template x-for="entry in unstagedEntries" :key="entry.path">
<button
class="item"
:class="{ active: selected?.path === entry.path }"
@click="select(entry)"
>
<div class="meta-row">
<div class="item-name" x-text="entry.name"></div>
<span class="badge warn" x-show="entry.partiallyStaged">partial</span>
</div>
<div class="item-path" x-text="entry.spec"></div>
</button>
</template>

<div class="section-title" x-show="stagedEntries.length">Staged</div>
<template x-for="entry in stagedEntries" :key="entry.path">
<button
class="item"
:class="{ active: selected?.path === entry.path }"
@click="select(entry)"
>
<div class="item-name" x-text="entry.name"></div>
<div class="item-path" x-text="entry.spec"></div>
</button>
</template>
</div>
</aside>

<main class="main">
<header class="topbar">
<div class="toolbar">
<div class="segmented">
<button :class="{ active: mode === 'side' }" @click="mode = 'side'">Side by side</button>
<button :class="{ active: mode === 'slider' }" @click="mode = 'slider'">Slider</button>
<button :class="{ active: mode === 'blink' }" @click="mode = 'blink'">Blink</button>
</div>
<template x-if="mode === 'blink'">
<label class="check-row">
<span>Seconds</span>
<input class="blink-range" type="range" min="0.15" max="3" step="0.05" x-model.number="blinkSeconds" />
<span x-text="blinkSeconds.toFixed(2)"></span>
</label>
</template>
<div class="spacer"></div>
<button class="primary" :disabled="!selected || selected.staged" @click="stageSelected()">git add</button>
<button @click="copyCurrentNote()" :disabled="!selected">Copy note</button>
<button @click="copyAllNotes()">Copy all notes</button>
</div>
<div class="path" x-text="selected?.path || 'No screenshot selected'"></div>
<div class="status" :class="{ error: statusIsError }" x-text="status"></div>
</header>

<section class="viewer" x-show="selected">
<template x-if="mode === 'side'">
<div class="side-grid">
<div class="frame">
<div class="frame-label">Before</div>
<div class="image-wrap">
<img :src="beforeUrl()" alt="Before screenshot" />
</div>
</div>
<div class="frame">
<div class="frame-label">After</div>
<div class="image-wrap">
<img :src="afterUrl()" alt="After screenshot" />
</div>
</div>
</div>
</template>

<template x-if="mode === 'slider'">
<div class="compare">
<div class="slider-stage" x-ref="sliderStage" :style="`--image-width: ${sliderWidth}px`">
<img :src="beforeUrl()" alt="Before screenshot" @load="syncSliderWidth()" />
<div class="after" :style="`width: ${slider}%`">
<img :src="afterUrl()" alt="After screenshot" />
</div>
</div>
<input class="slider-range" type="range" min="0" max="100" x-model.number="slider" />
</div>
</template>

<template x-if="mode === 'blink'">
<div class="compare">
<div class="blink-stage">
<img :src="blinkShowAfter ? afterUrl() : beforeUrl()" alt="Blink screenshot" />
</div>
<div class="meta-row">
<span class="badge" x-text="blinkShowAfter ? 'after' : 'before'"></span>
<button @click="blinkPaused = !blinkPaused" x-text="blinkPaused ? 'Resume' : 'Pause'"></button>
</div>
</div>
</template>
</section>

<section class="empty" x-show="!selected">
<div>No changed screenshots found.</div>
</section>

<footer class="notes">
<div class="meta-row">
<strong>Notes</strong>
<span class="count" x-show="selected" x-text="selected.name"></span>
</div>
<textarea
:disabled="!selected"
x-model="currentNote"
@input="saveNote()"
placeholder="Review note"
></textarea>
</footer>
</main>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing optional chaining on selected.name.

On line 498, x-text="selected.name" will throw a TypeError when selected is null. Unlike x-if, x-show only toggles visibility—the element remains in DOM and the expression is still evaluated. Other bindings in this file correctly use optional chaining (e.g., line 399: selected?.path).

🐛 Proposed fix
-          <span class="count" x-show="selected" x-text="selected.name"></span>
+          <span class="count" x-show="selected" x-text="selected?.name"></span>
🤖 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/screenshot-reviewer.html` around lines 379 - 508, The x-text binding
in the notes footer uses selected.name which throws when selected is null;
update the binding to use optional chaining (selected?.name) so the expression
is safe when the element is hidden via x-show; locate the notes footer block
(the element with <span class="count" x-show="selected" x-text="selected.name">)
and change the x-text to use selected?.name to match other bindings like
selected?.path.

Comment on lines +138 to +159
async function handleImage(requestUrl, response) {
const path = requestUrl.searchParams.get('path');
const version = requestUrl.searchParams.get('version');
const { absolutePath, relativePath } = resolveScreenshotPath(path);

response.writeHead(200, {
'content-type': 'image/png',
'cache-control': 'no-store',
});

if (version === 'head') {
const buffer = await runGit(['show', `HEAD:${relativePath}`]);
response.end(buffer);
return;
}

if (version !== 'worktree') {
throw new Error('Invalid image version');
}

createReadStream(absolutePath).pipe(response);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing error handler on read stream.

If the worktree file doesn't exist or is unreadable, createReadStream emits an 'error' event that's not caught. This bypasses the try/catch wrapper (stream errors are async) and could cause an unhandled exception or leave the response hanging.

🛡️ Proposed fix to handle stream errors
-  createReadStream(absolutePath).pipe(response);
+  const stream = createReadStream(absolutePath);
+  stream.on('error', (err) => {
+    if (!response.headersSent) {
+      sendJson(response, 500, { error: err.message });
+    } else {
+      response.destroy(err);
+    }
+  });
+  stream.pipe(response);
🤖 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/screenshot-reviewer.mjs` around lines 138 - 159, handleImage
currently pipes createReadStream(absolutePath) to response without handling
stream errors; attach an 'error' listener to the stream returned by
createReadStream in the worktree branch (after resolveScreenshotPath and the
version checks) to catch file-not-found or read errors, log or include the error
via your existing logger, send an appropriate response (e.g.,
response.writeHead(404 or 500) and response.end()) and ensure you remove/cleanup
listeners as needed so the response cannot hang or throw an unhandled exception
when the file is unreadable.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

Copy link
Copy Markdown
Contributor

@wayfarer3130 wayfarer3130 left a comment

Choose a reason for hiding this comment

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

Lets wait till we can include an update to cs3d build versions before merging, but the changes look good.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

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.

2 participants