MDV release checklist
Goal: release a @spatialdata/vis version that MDV can consume now, without replacing Viv, but using this in-place where possible.
Scope constraints:
- keep Viv as the raster/image foundation
- keep this repo focused on SpatialData-oriented rendering/library contracts
- ensure MDV can compose its own deck layers and state cleanly alongside this library
- avoid app-specific abstractions leaking into
@spatialdata/*packages
What "ready for MDV" means
A version is MDV-ready when MDV can embed SpatialCanvasViewer as a headless renderer while preserving existing chart behavior from:
~/code/www/MDV/src/react/components/VivScatterComponent.tsx~/code/www/MDV/src/react/scatter_state.ts~/code/www/MDV/src/react/spatial_context.tsx(current replacement for expectedSpatialLayers.ts)
Integration contract for this release
Use this as the contract boundary between this repo and MDV.
MDV-controlled state (primary model)
MDV is the source of truth for viewer state. SpatialCanvasViewer is a controlled renderer: it accepts MDV state and produces deck/Viv output; it does not own chart config or selection semantics.
MDV passes and updates (from chart / datastore / MobX):
spatialData,coordinateSystemrenderStack.entries— canonical order plus visibility, opacity, per-layer style, and (for shapes and labels) filter/colour driven by table rowsviewState+onViewStateChangehostLayerResolverfor overlays MDV builds directly, plus optionaldeckPropsfeatureColorResolverwhen MDV wants to hand over precomputed per-feature RGBA instead of a per-id record (runtime, not serialized)
When MDV selection or colour mappings change, MDV updates the relevant stack entry props and React re-renders the viewer. This repo loads SpatialData geometry/images and maps MDV’s render stack to Viv/deck output.
What @spatialdata/vis owns
- SpatialData-backed discovery and async loading (images, shapes, labels)
- Viv image path and labels bitmask path
- Turning caller-supplied render stack entries into deck layers (
useLayerData/ renderers) - Composing generated layers with host overlays resolved from stack descriptors
- Stable picking/tooltip hooks keyed by layer id (for MDV tooltip portals)
What MDV owns (v1)
- Chart config and app state (MobX / zustand)
- Filtering and highlighting semantics (same as table rows today)
- Scatter, gates, contours, ROI JSON, and other bespoke deck layers
- View linking (
useViewStateLink), outer tooltip (useOuterContainerDeckTooltip) - Viv extensions on image channels (passed through layer config /
deckProps; regressions here are bugs)
v1 layer scope
| Layer kind | v1 |
|---|---|
| Images | Yes (Viv) |
| Shapes | Yes — MDV drives style/filter via render stack entry props |
| Labels | Yes — same featureState / fillColorByColumn API as shapes; segmentation vs shapes experiment is not a release gate |
| Points | v1.1 — feature selection and colour are config-driven and serializable; viewport tiling exists for Morton-sorted and points-zarr elements |
Custom deck layers (additive, not a replacement for state)
MDV can still resolve layers it constructs entirely in app code (for example
from spatial_context.ts) from host overlay descriptors in renderStack.entries.
Those can interleave with SpatialData-generated layers in the composed stack.
Over time, patterns that repeat in MDV (shared shape styling, common overlays) may move into @spatialdata/vis or @spatialdata/layers; v1 does not require that migration.
Shapes v1: filter and colour via render stack entry props
For v1, shapes must support the same effect as MDV table-row colouring/filtering, without MDV re-implementing SpatialData loading:
-
ShapesLayerConfigaccepts MDV-driven per-feature style (featureState,fillColorByColumnon the config object MDV updates when selection changes) - Feature index /
instance_keyalignment with the associated table is stable for picking and styling (coreFeatureTableAlignment+ layers encoders) - Document that MDV updates the shapes entry's props when datastore selection or colour columns change (Headless viewer guide, Feature table associations)
Current local state:
@spatialdata/coreexposesFeatureTableAlignment/createFeatureTableAlignment()as the shared row resolver shape, following Pythonspatialdataregion_key/instance_keysemantics.@spatialdata/layersownsbuildShapeFillColorByFeatureId()for converting a resolvedrowIndexByFeatureIndexplus a table column into per-feature colours.- Remaining release risk: tooltip and pick-event row resolution should finish converging on the shared core resolver shape before treating the association contract as stable.
Optional later: MDV-only PolygonLayer built from exported geometry helpers; not required if config-driven styling is sufficient.
Labels: filter and colour via the same layers state
Labels take the same API, so MDV drives them the same way — no separate code path:
-
LabelsLayerConfigacceptsfeatureStateandfillColorByColumn, with the same field names and meanings as shapes. The one omission isstrokeColorByFeatureId: a label's outline is derived from its fill in the bitmask shader. -
spatialLabelsSublayerSchemacarriesfeatureState, so the olderSpatialLayerPropssurface can express per-label filtering too. - Colour resolves through a label-id-indexed LUT the shader samples, so a selection change re-uploads a small table rather than the tiles; picking consults the same table, so a hidden label cannot be picked.
- Hovering a label highlights it, as
autoHighlightdoes for shapes. MDV configures nothing: it followshoverTooltipModeand is driven from the same pick as the tooltip. It is runtime render state, so it never appears in a saved Render Stack entry — do not try to persist or restore it.
The one thing MDV must get right is the id: a labels feature id is the label's integer instance id as a string — the raster's own pixel value, and the same id the tooltip resolves against the associated table.
For colour MDV computes itself (a derived column, a live selection), prefer the
runtime featureColorResolver on SpatialCanvasViewer over a
featureId → colour record; it takes the RGBA bytes directly. It is deliberately
not part of the saved Render Stack.
Points v1.1: feature selection and colour via render stack entry props
Points support the same config-driven model as shapes — MDV updates the layer config, vis owns loading and rendering:
-
PointsLayerConfigaccepts a serializable selection (featureNames), per-feature colour (featureColorOverrides),colorByFeature,pointSize,pointsMemoryCap - Selections persist by feature NAME, not code, so a saved config survives catalog renumbering (Headless viewer guide)
- Colour overrides keyed by feature name for the same reason
- Headless feature UI supported without vis internals:
PointsFeatureStateProvider+usePointsFeatureState - Viewport-tiled loading for very large Morton-sorted and points-zarr elements (Loading points and shapes)
- Benchmarking index strategies; Python writer utilities
The serialization detail that matters for saved MDV charts: a points element
frequently has no feature-code column in the file — a Xenium transcripts
carries feature_name only — so codes are assigned by the application while
building the feature catalog and are not stable across catalog upgrades, catalog
paths, or servers. featureNames is therefore the durable field; featureCodes
exists for runtime and backwards compatibility and should not be persisted.
Current local state:
@spatialdata/coreexposesresolveFeatureSelectionCodes()andfeatureNamesForCodes()for converting between the durable and runtime forms against a given catalog.- The feature catalog is two-stage: an instant resident-subset preview, then the authoritative full-dataset list. A selection made against either resolves to the same features, which is what the name form buys.
- Remaining release risk: a selection whose points fall outside the resident
window needs a whole-dataset scan, whose cost scales with element size; tiled
elements avoid it.
matchingLoadStateexposes the progress meanwhile. - Remaining release risk: for a dictionary-only element, the fallback catalog path cannot tally counts, and it settles successfully without them — so the retry path does not repair it and the counts stay absent for the session. Do not treat count ordering or the presence of counts as a signal that the scan finished; use the names, and render a missing count as unknown rather than zero.
Bespoke layer extension path
- Primary extension for state-owned SpatialData layers: MDV-controlled
renderStack.entries - Primary extension for fully custom deck layers: host overlay entries plus
hostLayerResolver - Composition order: canonical render-stack order, with unmanaged deck layers treated as compatibility output
Practical checks:
- MDV can drive a shapes layer only by updating render stack entry props (no
SpatialCanvaszustand store) - An MDV-style custom layer from
spatial_context.tscan be supplied through a host overlay resolver without importingpackages/vis/src/*internals
Release checklist
0) npm release setup
-
zarrextrais published outside the@spatialdata/*namespace. - Changesets is configured with a fixed
@spatialdata/*suite:@spatialdata/core@spatialdata/react@spatialdata/layers@spatialdata/avivatorish@spatialdata/vis
-
zarrextrais released with the suite but can version independently in future releases. - Release automation creates the Changesets version PR only; npm publishing is manual for now.
- No npm publish token is required in GitHub repository secrets.
- Publish manually from a clean local checkout using
pnpm publish:latest(defaultlatestdist-tag). -
@spatialdata/visinstalls cleanly in MDV.
1) API and export sanity
-
@spatialdata/visexportsSpatialCanvasVieweras public API. - Viewer helper exports are public and documented:
useSpatialCanvasRenderercomposeSpatialDeckLayersshouldAutoFitSpatialViewshouldRenderInternalTooltip
-
SpatialViewersupports passthroughdeckPropssafely. - No MDV integration requires importing from
packages/vis/src/...internals (public entry only).
2) Headless behavior parity checks
-
SpatialCanvasViewerrenders with externally controlled:coordinateSystemrenderStackviewState
- It composes host overlays with SpatialData-rendered layers.
- It allows MDV-owned tooltip flow (
renderTooltip={false},onFeatureHover). - It supports MDV-owned controller settings through
deckProps.
3) MDV touchpoint checks
-
VivScatterComponent.tsxstyle embedding is possible without sidebars/editor UI fromSpatialCanvas. -
scatter_state.ts-style scatter/filter/highlight layers can be passed as external deck layers. -
spatial_context.tsx-style selection and editable overlay layers can be passed through unchanged. - View ID and layer ID conventions remain compatible with MDV filtering assumptions (current
getVivId(...)patterns).
4) Version and packaging hygiene
-
pnpm -r --filter @spatialdata/vis testpasses. - package build succeeds for
@spatialdata/visand dependent workspace packages. - produce installable package(s) for MDV testing (published
latestor packed tarballs). - smoke-test install in MDV workspace and verify viewer renders.
5) Non-goals (explicit)
- No attempt to replace Viv in this release.
- No 3D rendering (orbit views, volume rendering, pitch/bearing navigation) in this release — 2D Cartesian only.
- No in-repo implementation of broad GIS support in this release.
- No hard coupling of
coreAPI to deck-specific loaders for this release.
Known risks
Tracked issues that are not blockers for MDV:
- #41 — very large shape/image inputs can exhaust browser memory; add resource estimates and guard rails before broad use.
- #33 — stale elements can appear after switching stores when element names overlap.
- #12 — parquet-wasm loading can fail in the vis dev server.
- #13 — image source changes can temporarily hit an invalid source and surface runtime errors.
- #24 — points parquet loading should fail gracefully when server responses are directory listings or otherwise misconfigured.
Later unless MDV needs them immediately:
- #11 — multipart parquet sources (
part.N.parquetdirectories are read; the issue is open). - #16 — 3D volumetric state/UI.
- #6 — local file selection in the UI (
FolderStore.fromFilesandZipStorealready open picked folders and archives).
Viv extensions and the Viv 0.23.0 stack
This repo pins @hms-dbmi/viv@0.23.0 and deck.gl ~9.4.0 (the catalog in pnpm-workspace.yaml); Viv's uniform-buffer-backed shader props, model.shaderInputs and variable channel counts date from 0.21. Confirm MDV is on the same stack; the remaining gate is extension behaviour under it.
Why this matters now
- MDV relies on Viv extensions and deck composition behavior today.
- Extension breakage can look subtle (missing colormap behavior, stale shader props, or dropped extension props during cloning/spreading).
- The move from deprecated uniform paths to shader-input paths can break custom layers/extensions if we do not audit deliberately.
Viv-upgrade readiness
- Align Viv / deck.gl in this repo (
0.23.0/~9.4.0). - Confirm MDV is on the same stack.
- Audit
@spatialdata/visViv paths for extension prop preservation:packages/vis/src/SpatialCanvas/VivSpatialViewer.tsxvivPropspassthrough intogetLayers({ props }); no post-spread oflayer.props.
- Issue #56 APIs:
useLayerChannelState,useImageLayerContext,getChannelSelectionStats, Viv extension resolvers — see MDV integration roadmap. -
/headlessdemo visual check: brightness/contrast sliders affect image layer viavivImagePropsResolver. - Audit channel-count assumptions and constants:
packages/avivatorish/src/constants.tspackages/vis/src/SpatialCanvas/ImageChannelPanel.tsxpackages/vis/src/SpatialCanvas/LabelsChannelPanel.tsx- ensure behavior is coherent if Viv supports wider channel ranges.
- Audit shader/uniform update paths in custom deck/Viv-adjacent layers:
packages/layers/src/LabelsBitmaskTileLayer.ts- confirm
shaderInputs.setProps(...)path is primary and anysetUniforms(...)usage is fallback-only.
- Verify app-level extension passthrough via
vivLayerProps,vivImagePropsResolver, andvivImageExtensionResolveronSpatialCanvasViewer. - Run visual smoke tests with extension-heavy image views (2D at minimum) before publishing package versions.
Fast acceptance criteria
- No regression in extension-driven rendering for image layers (including opacity/channel state updates).
- No regression in composed app overlays when Viv layers are present.
- No obvious channel-limit regressions introduced by mismatched constants.
- No hard dependency on deprecated uniform-only update patterns in custom layers/extensions.
Suggested MDV smoke-test sequence
- Install
@spatialdata/vis(published or packed) into MDV. - Add a minimal wrapper chart that renders
SpatialCanvasViewerwith existing MDV view state. - Pass current MDV overlays from
VivScatterComponent.tsxas host overlay entries resolved byhostLayerResolver. - Route tooltip through MDV's outer-container tooltip path only.
- Validate pan/zoom linking and selection behavior parity.
- Validate at least one labels/shapes overlay above image.
Follow-up work after release
After this MDV-ready release, pursue incremental improvements in separate PRs:
- formalize stable layer identity contract for cross-layer picking/highlighting
- broaden feature-property contract beyond tooltip fields
- improve points styling/filtering parity with MDV needs
- evaluate optional Arrow-friendly data paths without forcing deck dependencies into
core
Future consideration: editable layers and write support
Not in v1. v1 keeps selection/annotation as MDV-owned deck layers supplied as host overlays (for example editable ROI / lasso layers from spatial_context.ts), with this repo focused on read paths into SpatialData.
Later, consider implementing here (and/or in @spatialdata/core) when write APIs exist:
- Editable overlay layers — drawing, editing vertices, selection geometries synced with chart state (today MDV implements these; candidates to lift into
@spatialdata/visif the interaction model stabilizes). - Annotation persistence — round-trip edits to SpatialData-compatible stores (shapes tables, labels, or sidecar layers), not only ephemeral deck state.
- General write support — align with
@spatialdata/coremoving beyond read-only (see core overview); vis would expose mutation helpers only once core defines safe, tested write contracts.
Design constraints to decide before building:
- Whether edits target Zarr/SpatialData on disk, in-memory session state, or MDV’s own project files.
- How editable layers share picking ids and coordinate systems with read-only SpatialData layers.
- Whether Vitessce/MDV both need the same editable-layer API or app-specific extensions remain sufficient.