MDV release checklist
Goal for this PR branch: 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 layers state |
| 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; tiling/index strategy still in progress |
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 layers state
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
layers[layerId]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 layers state
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 - Tiling / index strategy for very large elements (follow-up: 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 — this
is what the tiling/index follow-up addresses.
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 for this branch:
- 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 (this branch)
0) npm prerelease setup
-
zarrextrais published outside the@spatialdata/*namespace. - Changesets is configured with a fixed
@spatialdata/*prerelease suite:@spatialdata/core@spatialdata/react@spatialdata/layers@spatialdata/avivatorish@spatialdata/vis
-
zarrextraparticipates in the initial prerelease 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 (npm
nextprerelease or 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 alpha risks
Track these as explicit prerelease risks rather than blockers to the first MDV smoke package:
- #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.
Post-prerelease unless MDV needs them immediately:
Viv extensions and Viv 0.21.0 stack
This repo pins @hms-dbmi/viv@0.21.0 and deck.gl 9.2.9 (Viv PR #924: uniform-buffer-backed shader props, model.shaderInputs, variable channel counts). MDV's matching Viv/deck upgrade is almost ready to merge — treat version alignment as done; the remaining gate is extension behaviour under the shared stack.
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.
Branch checklist for Viv-upgrade readiness
- Align Viv
0.21.0/ deck.gl9.2.9in this repo and MDV (MDV merge imminent). - 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 branch build of
@spatialdata/visinto MDV. - Add a minimal wrapper chart that renders
SpatialCanvasViewerwith existing MDV view state. - Pass current MDV overlays from
VivScatterComponent.tsxasdeckProps.layers. - 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 passed through deckLayers (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.