feat: consume dmv deck.gl bulk annotations; drop clustering UI - #405
feat: consume dmv deck.gl bulk annotations; drop clustering UI#405igoroctaviano wants to merge 23 commits into
Conversation
Depend on dicom-microscopy-viewer@b9925eb (feat/bulk-ann-deckgl). Remove obsolete clustering settings/state and wire measurement limitValues via getAnnotationGroupMeasurementRange.
|
|
Overall Grade |
Security Reliability Complexity Hygiene |
Code Review Summary
| Analyzer | Status | Updated (UTC) | Details |
|---|---|---|---|
| JavaScript | Aug 12, 2026 11:26a.m. | Review ↗ |
Important
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
… null measurement range - pin dicom-microscopy-viewer to 51a7c31 (code-review fixes: affine resolution, render triggers, view-change LOD refresh, rotation support, measurement filter wiring) - remove the corrupted 'set this to true or false' placeholder pnpm wrote into pnpm-workspace.yaml and document that the tarball-URL allowBuilds entry must track the dependency pin - handle getAnnotationGroupMeasurementRange returning null before the viewer has fetched measurement values
Code review pass — fixes pushed in 575bb42
|
… baseURL fixes - explicitly send measurement: undefined when 'None' is selected so the viewer deactivates measurement-based filtering (previously only the color was reset and the filter stayed active) - bump dicom-microscopy-viewer to b0fcc04: LOADING_STARTED/ENDED are now always paired (prevents a stuck global loading indicator when a group is hidden mid-load) and the streaming fallback uses client.baseURL
Review round 2 (2774481)Second review pass. One real bug found and fixed, plus a dmv bump:
Known limitation (unchanged): if a measurement is selected before the viewer has fetched measurement values, ✅ biome clean · tsc clean · production build OK · 37/37 tests pass (the |
The dicom-microscopy-viewer dependency is now installed from a git commit and must run its prepare script to produce dist/. Build scripts remain gated by the allowBuilds allowlist in pnpm-workspace.yaml, so dropping --ignore-scripts keeps supply-chain protection.
The previous pin failed to prepare on GitHub Actions because dmv's babel exclude on /(node_modules)/ matched the setup-pnpm store path, skipping import.meta transforms. dmv 6041fe0 scopes the babel rule with include: src instead.
|
Visit the preview URL for this PR (updated for commit 1149ac8): https://idc-external-006--pr405-feat-bump-dmv-bulk-a-anpo4smb.web.app (expires Wed, 19 Aug 2026 11:29:21 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: 88aacecd98ba54d2f9c8d201a9444e43d1ad8307 |
Rule githubactions:S6505 flags pnpm install without --ignore-scripts, but the git-hosted dicom-microscopy-viewer needs its prepare script to build dist/ at install time. Scripts stay gated per-package by the allowBuilds allowlist in pnpm-workspace.yaml. Revert once dmv is pinned to a published npm release.
CI green (298472a)All checks now pass. Three issues were blocking CI, all specific to installing dicom-microscopy-viewer from a git commit instead of a published npm release:
Note: |
Drives the real app in a WebGL (SwiftShader) browser and compares the deck.gl bulk-annotation overlay against a committed screenshot baseline, so rendering regressions are caught automatically. Default target is the ~396k-polygon TCGA-02-0001 "Nuclei" group on the public IDC proxy. - playwright.config.ts: deterministic software WebGL2, per-OS baselines, serves a prod build baked with the new e2e config, retries in CI - e2e/: whole-slide visual-regression spec plus a memory/OOM guardrail and a toggle-off smoke test; Linux baseline generated via the pinned Playwright container (e2e/update-snapshots-docker.sh) - .github/workflows/e2e-visual.yml: runs the suite inside that same container so it matches the committed baselines - public/config/e2e.js: proxy-backed config with 429/5xx retry
serve@14 crashes under path-to-regexp v8 (pathToRegExp.compile is not a function), which would make Playwright's webServer fail to start in CI. scripts/serve-e2e.mjs is a dependency-free static server with SPA fallback for /studies/... deep links.
- serve-e2e.mjs: open()+fstat instead of existsSync on request paths (Sonar S6549), reject directories so createReadStream cannot hang - playwright.config.ts: drop the no-op reporter ternary (Sonar S3923) - Exclude e2e/, serve-e2e.mjs, and playwright.config.ts from Sonar and DeepSource — they are test infrastructure, not production app code - Address remaining DeepSource nits in the Playwright helpers
The whole-slide screenshot was flaky for two independent reasons: 1. /studies/:uid sometimes redirected to a different SM series (this study has four slides, each with its own Nuclei group) — pin the DX1 series UID in the URL. 2. WSI tile decoding varies run-to-run — hide non-WebGL canvases and chrome before toHaveScreenshot so only the deck.gl overlay is compared. Regenerated the Linux baseline against the production e2e build.
After merging master (slim#318 THUMBNAIL pyramid), the stale 6041fe04 pin lacked the matching exact-resolution fix and crashed slide load in visual-regression CI. Point at 1b39614 and keep allowBuilds in sync.
# Conflicts: # package.json # pnpm-lock.yaml
…gs popover Add a Filled switch and fill-opacity slider to the bulk annotation group Display Settings popover (closed graphic types only: POLYGON, RECTANGLE, ELLIPSE), plumbed through to dicom-microscopy-viewer's setAnnotationGroupStyle. Also fix inconsistent Row/Col spans and a missing gutter across the popover (ColorSlider, OpacitySlider, and the Exploration measurement row) so labels and controls line up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The LOADING_STARTED/LOADING_ENDED events dicom-microscopy-viewer publishes around bulk annotation group hydration were never wired up beyond a cursor:progress CSS change, so toggling a group on gave no visible feedback. Track per-group load state and surface it inline next to the group's visibility switch: a percentage ring when byte progress is known (streamed Range retrieval), otherwise an indeterminate spinner. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Exploration measurement row squeezed a label ("Measurement") and a
full Select into 6/18 grid columns that were too narrow, truncating the
label and cramping the dropdown. Give it its own full-width stacked
layout (label above, select below) instead, matching the pattern used
for other full-width selects in the app. The same overflow would have
hit "Fill opacity" the moment Filled was toggled on, so shorten that
label to "Fill" (redundant anyway under the "Fill" divider).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ayout
Replace the tiny per-item spinner (easy to miss, no context) with a
floating card over the viewport, following the same presentation used
for viv-loader's bulk annotation progress: a spinner/checkmark per
group, a bold label, a phase description ("Fetching annotation
index…", "Retrieving annotation data… 42%", "Decoding & rendering…",
"Loaded"), and elapsed time. Settled rows (done/error) linger briefly
then auto-dismiss, matching viv-loader's UX.
Also fix the Display Settings sliders: the inline label+slider+input
Row/Col layout squeezed labels against sliders and could wrap the label
under the slider depending on content width. Switch Color/Opacity/Fill/
Values-of-interest to a stacked label-above layout (flex row for the
slider+input beneath), which is robust regardless of container width.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous pass put raw flex styles directly on antd Slider
(style={{flex:1, minWidth:0}}) to fix a label-width issue. That broke
Slider's track rendering in the real app (handle position was right,
but the colored/gray rail vanished) — and since ColorSlider/OpacitySlider
are shared by Segment, OpticalPath, Mapping, and ROI color settings too,
the regression wasn't scoped to annotation groups. Revert to the
already-proven Row/Col span-based sizing (no custom style on Slider)
used everywhere else in the app; keep the label-above-control layout
that fixed the original truncation, since that part was never the
problem.
Also rebuild the floating load-progress card: switch to antd Card
(matching the same component Description.tsx already uses for the
adjacent metadata panel) with sharp corners and an explicit white
background — Card doesn't set one by default, which is fine sitting in
a white sidebar but left the card transparent over the dark slide image.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AppShell already constrains the app to height:100vh + overflow:hidden, but antd portals Popover/Dropdown/Tooltip content to <body> by default — a sibling of AppShell, not a descendant, so it wasn't covered. A tall enough popover (Display Settings, with Fill/Exploration expanded) could grow past the viewport and trigger a page scrollbar, shifting the whole layout sideways for as long as it was open. Set overflow:hidden on html/body, matching the app's own no-page-scroll design; the popover body itself gets a max-height + internal scroll as a safety net so content never becomes unreachable if it doesn't fit. Also throttle the annotation-group style dispatch (color, opacity, fill opacity, value-of-interest limits): Slider's onChange fires on every pixel of drag, and each call was going straight into the viewer's full layer rebuild, making the drag itself feel laggy for larger groups. Local state (the visible slider position/number) still updates on every tick; the expensive dispatch into the viewer is now capped at ~20/s (leading+trailing, so neither the first tick nor the final value is ever dropped). Verified 30 rapid calls collapse to 2 actual dispatches. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…left Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the antd-based Display Settings popover (color/opacity/fill/ measurement controls) with a dedicated component built on Tailwind CSS and Radix UI primitives (Popover, Slider, Switch), for a cleaner, more compact layout than antd's Row/Col grid allowed. Scoped narrowly to avoid disturbing anything else in the app: - New components live under src/components/ui/ and are only imported by the new AnnotationGroupDisplaySettings.tsx — ColorSlider.tsx and OpacitySlider.tsx (still antd-based) are untouched, since Segments, Optical Paths, and Mappings share them too. - Tailwind's preflight reset is disabled (tailwind.config.js) so it doesn't also reset antd's own markup elsewhere; border-box sizing is instead scoped to just this component tree via a `.dmv-ui` class (src/tailwind.css), applied on the popover trigger and content. - react-scripts 5 has built-in Tailwind support (auto-detects tailwind.config.js), so no craco postcss override was needed — craco's own postcss-loader patching turned out to be incompatible with craco-less's loader chain anyway.
…ed controls Disabling Tailwind's global `preflight` (to avoid resetting antd elsewhere) also stripped `border-style: solid` from the new Display Settings components. Every element still got the right border color and width, but without an explicit style the browser fell back to its native default border style for buttons/inputs — an inset/outset 3D bevel — which read as a legacy OS dialog rather than a flat, modern panel. Adds a proper scoped mini-preflight (box-sizing, border-style, and the button/input/select resets preflight normally provides) under a `.dmv-ui` class instead of skipping it outright. The scope selector is wrapped in `:where()` so it doesn't gain extra specificity over a plain utility class — Tailwind v3's `@layer` only orders rules in the output rather than using native CSS cascade layers, so without this a selector like `.dmv-ui button` (0,1,1) would beat `.bg-neutral-200` (0,1,0) regardless of source order, which is what made the Filled switch render with a fully transparent (invisible) track until fixed. Also adds subtle shadow-sm to inputs/slider thumbs/the select for a bit more depth, without introducing rounded corners.
|




Summary
Companion to dicom-microscopy-viewer#271.
Bulk Microscopy Simple Annotations are now rendered by a deck.gl overlay inside dmv, so slim no longer needs the OpenLayers clustering controls.
Changes
dicom-microscopy-viewer@b9925eb(feat/bulk-ann-deckgl) via GitHub (allowBuilds for prepare/webpack)SlideViewerstate /constructViewerslimitValuesthroughgetAnnotationGroupMeasurementRange(new dmv API) so the existing slider drivesDataFilterExtensionfiltering once measurements are hydratedNotes
"dicom-microscopy-viewer": "^0.49.0"and drop the git allowBuilds entry.Test plan
tsc --noEmit, production build, unit tests (pre-push)REACT_APP_CONFIG=example pnpm start→ open the IDC ANN series, toggle large polygon groups, confirm progressive paint / no OOM, measurement slider + ROI pick/zoom still work