Skip to main content

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 expected SpatialLayers.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, coordinateSystem
  • renderStack.entries — canonical order plus visibility, opacity, per-layer style, and (for shapes and labels) filter/colour driven by table rows
  • viewState + onViewStateChange
  • hostLayerResolver for overlays MDV builds directly, plus optional deckProps
  • featureColorResolver when 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 kindv1
ImagesYes (Viv)
ShapesYes — MDV drives style/filter via render stack entry props
LabelsYes — same featureState / fillColorByColumn API as shapes; segmentation vs shapes experiment is not a release gate
Pointsv1.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:

  • ShapesLayerConfig accepts MDV-driven per-feature style (featureState, fillColorByColumn on the config object MDV updates when selection changes)
  • Feature index / instance_key alignment with the associated table is stable for picking and styling (core FeatureTableAlignment + 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/core exposes FeatureTableAlignment / createFeatureTableAlignment() as the shared row resolver shape, following Python spatialdata region_key / instance_key semantics.
  • @spatialdata/layers owns buildShapeFillColorByFeatureId() for converting a resolved rowIndexByFeatureIndex plus 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:

  • LabelsLayerConfig accepts featureState and fillColorByColumn, with the same field names and meanings as shapes. The one omission is strokeColorByFeatureId: a label's outline is derived from its fill in the bitmask shader.
  • spatialLabelsSublayerSchema carries featureState, so the older SpatialLayerProps surface 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 autoHighlight does for shapes. MDV configures nothing: it follows hoverTooltipMode and 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:

  • PointsLayerConfig accepts 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/core exposes resolveFeatureSelectionCodes() and featureNamesForCodes() 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. matchingLoadState exposes 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 SpatialCanvas zustand store)
  • An MDV-style custom layer from spatial_context.ts can be supplied through a host overlay resolver without importing packages/vis/src/* internals

Release checklist​

0) npm release setup​

  • zarrextra is published outside the @spatialdata/* namespace.
  • Changesets is configured with a fixed @spatialdata/* suite:
    • @spatialdata/core
    • @spatialdata/react
    • @spatialdata/layers
    • @spatialdata/avivatorish
    • @spatialdata/vis
  • zarrextra is 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 (default latest dist-tag).
  • @spatialdata/vis installs cleanly in MDV.

1) API and export sanity​

  • @spatialdata/vis exports SpatialCanvasViewer as public API.
  • Viewer helper exports are public and documented:
    • useSpatialCanvasRenderer
    • composeSpatialDeckLayers
    • shouldAutoFitSpatialView
    • shouldRenderInternalTooltip
  • SpatialViewer supports passthrough deckProps safely.
  • No MDV integration requires importing from packages/vis/src/... internals (public entry only).

2) Headless behavior parity checks​

  • SpatialCanvasViewer renders with externally controlled:
    • coordinateSystem
    • renderStack
    • viewState
  • 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.tsx style embedding is possible without sidebars/editor UI from SpatialCanvas.
  • 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 test passes.
  • package build succeeds for @spatialdata/vis and dependent workspace packages.
  • produce installable package(s) for MDV testing (published latest 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 core API 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.parquet directories are read; the issue is open).
  • #16 — 3D volumetric state/UI.
  • #6 — local file selection in the UI (FolderStore.fromFiles and ZipStore already 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/vis Viv paths for extension prop preservation:
    • packages/vis/src/SpatialCanvas/VivSpatialViewer.tsx
    • vivProps passthrough into getLayers({ props }); no post-spread of layer.props.
  • Issue #56 APIs: useLayerChannelState, useImageLayerContext, getChannelSelectionStats, Viv extension resolvers — see MDV integration roadmap.
  • /headless demo visual check: brightness/contrast sliders affect image layer via vivImagePropsResolver.
  • Audit channel-count assumptions and constants:
    • packages/avivatorish/src/constants.ts
    • packages/vis/src/SpatialCanvas/ImageChannelPanel.tsx
    • packages/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 any setUniforms(...) usage is fallback-only.
  • Verify app-level extension passthrough via vivLayerProps, vivImagePropsResolver, and vivImageExtensionResolver on SpatialCanvasViewer.
  • 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​

  1. Install @spatialdata/vis (published or packed) into MDV.
  2. Add a minimal wrapper chart that renders SpatialCanvasViewer with existing MDV view state.
  3. Pass current MDV overlays from VivScatterComponent.tsx as host overlay entries resolved by hostLayerResolver.
  4. Route tooltip through MDV's outer-container tooltip path only.
  5. Validate pan/zoom linking and selection behavior parity.
  6. 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/vis if 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/core moving 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.