Skip to main content

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 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 layers state
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; 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:

  • 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 layers[layerId] 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 layers state

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
  • 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/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 — this is what the tiling/index follow-up addresses. 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 for this branch:

  • 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 (this branch)

0) npm prerelease setup

  • zarrextra is published outside the @spatialdata/* namespace.
  • Changesets is configured with a fixed @spatialdata/* prerelease suite:
    • @spatialdata/core
    • @spatialdata/react
    • @spatialdata/layers
    • @spatialdata/avivatorish
    • @spatialdata/vis
  • zarrextra participates 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 (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 (npm next prerelease 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 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:

  • #11 — multipart parquet sources.
  • #16 — 3D volumetric state/UI.
  • #6 — local file selection.

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.gl 9.2.9 in this repo and MDV (MDV merge imminent).
  • 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 branch build of @spatialdata/vis 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 deckProps.layers.
  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 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/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.