Skip to content

Clarify prop-to-layer flow and move render stack ownership into layers - #49

Merged
xinaesthete merged 9 commits into
mainfrom
codex/clarify-propstolayers-flow
Jun 19, 2026
Merged

Clarify prop-to-layer flow and move render stack ownership into layers#49
xinaesthete merged 9 commits into
mainfrom
codex/clarify-propstolayers-flow

Conversation

@xinaesthete

@xinaesthete xinaesthete commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Clarifies the prop-to-layer contract across docs and architecture notes.
  • Moves render stack ownership into @spatialdata/layers and adapts @spatialdata/vis to consume it.
  • Updates SpatialCanvas, demos, and tests to reflect the new layer-driven flow.
  • Adds/updates documentation for headless viewer and MDV integration guidance.

Testing

  • Added and updated unit coverage for render stack behavior.
  • Updated SpatialCanvas viewer tests to validate the new prop routing and layer-driven rendering flow.
  • Performed local validation of the affected demos and viewer paths.

Summary by CodeRabbit

  • New Features

    • Introduced a RenderStack-based rendering API to replace separate layer configuration, providing a unified model for spatial and overlay layers with explicit ordering control.
    • Added support for custom "host overlays" that interleave seamlessly with spatial data layers through a resolver callback.
    • Implemented feature-level pick events (onFeatureHover, onFeatureClick) for improved runtime event handling.
    • Expanded documentation with architecture decisions, integration guides, and API migration examples.
  • Documentation

    • Updated comprehensive guides on headless rendering, layer configuration, and MDV integration reflecting the new stack-based architecture.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@xinaesthete, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 9 minutes and 21 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 21a0c7fd-7c3e-4627-b5b9-ca94262c8c57

📥 Commits

Reviewing files that changed from the base of the PR and between a6e4fa0 and a03f4a9.

📒 Files selected for processing (10)
  • .changeset/render-stack-events.md
  • packages/layers/src/renderStack.ts
  • packages/layers/tests/renderStack.spec.ts
  • packages/vis/src/SpatialCanvas/index.tsx
  • packages/vis/src/SpatialCanvas/layerConfig.ts
  • packages/vis/src/SpatialCanvas/public.ts
  • packages/vis/src/SpatialCanvas/renderStackAdapters.ts
  • packages/vis/src/SpatialCanvas/types.ts
  • packages/vis/src/index.ts
  • packages/vis/tests/spatialCanvasViewer.spec.ts
📝 Walkthrough

Walkthrough

Introduces a versioned RenderStack schema (Zod-validated, discriminated spatial/host/group entries) in @spatialdata/layers, adapter utilities in @spatialdata/vis that convert stack entries to deck.gl layers with stable ordering and host overlay resolution, a SpatialCanvasViewer refactor supporting renderStack/hostLayerResolver/onFeatureHover/onFeatureClick, demo migrations, and architecture documentation including a CONTEXT.md vocabulary reference and ADR.

Changes

RenderStack Schema, Adapters, Viewer Wiring, and Docs

Layer / File(s) Summary
RenderStack schema and barrel exports
packages/layers/src/renderStack.ts, packages/layers/src/index.ts, packages/vis/src/index.ts
Defines versioned Zod schemas for spatial, host, and group render-stack entries plus the top-level renderStackSchema, infers TypeScript types, adds getRenderStackEntryIds/getRenderStackHostLayerIds helpers, and re-exports everything from both package barrels.
RenderStack schema unit tests
packages/layers/tests/renderStack.spec.ts
Adds Vitest tests for renderStackSchema parse/reject, individual entry schemas (spatial, host, group), and helper function canonical ordering.
RenderStack adapter utilities
packages/vis/src/SpatialCanvas/renderStackAdapters.ts, packages/vis/src/SpatialCanvas/public.ts
Adds spatialEntryToLayerConfig, renderStackToLayerInputs, resolveRenderStackHostLayers, renderStackOrder, and sortLayersByRenderStackOrder; re-exports them as public surface.
RenderStack adapter integration tests
packages/vis/tests/spatialCanvasViewer.spec.ts
Adds describe('render stack adapters') tests covering spatial-to-LayerInputs normalization, host descriptor resolution, unknown-host callback, layer sorting by stack order, and group child ID ordering slots.
Feature pick event types and useLayerData hook
packages/vis/src/SpatialCanvas/useLayerData.ts
Adds ShapeFeaturePickEventData, LabelFeaturePickEventData, SpatialFeaturePickEventData interfaces, isLabelsAvailableElement/isShapesAvailableElement type guards, getPickedLabelObject helper, and the getFeaturePickEvent implementation, updating tooltip gating to use the new guards.
SpatialCanvasViewer renderStack wiring and pick callbacks
packages/vis/src/SpatialCanvas/SpatialCanvasViewer.tsx
Refactors SpatialCanvasViewer to accept renderStack, hostLayerResolver, onFeatureHover, and onFeatureClick; introduces useSpatialCanvasRendererFromLayerInputs; rewires SpatialCanvasViewerInner to derive layer inputs and host layers from the stack; updates hover/click handlers to emit SpatialFeaturePickEvent.
Internal SpatialCanvas store wiring and Viv compat
packages/vis/src/SpatialCanvas/index.tsx, packages/vis/src/SpatialCanvas/VivSpatialViewer.tsx
Updates SpatialCanvasInner to call useSpatialCanvasRendererFromLayerInputs with a layerInputs object; adds a Viv getDefaultInitialViewState modelMatrix compatibility cast.
Demo migration to renderStack
packages/vis/demo/src/buildHeadlessLayers.ts, packages/vis/demo/src/CodecFixtureDemo.tsx, packages/vis/demo/src/HeadlessBlobsDemo.tsx, packages/vis/demo/tsconfig.json
Migrates buildHeadlessLayers to emit RenderStack; updates both demos to maintain renderStack state, toggle entry visibility, wire onFeatureHover/onFeatureClick, and render layer checkboxes from entries; disables unused-code TS linting.
Documentation and ADR updates
CONTEXT.md, docs/adr/0001-render-stack-owned-by-layers.md, docs/docs/intro.mdx, docs/docs/layers/overview.mdx, docs/docs/vis/...
Adds vocabulary reference (CONTEXT.md), ownership ADR, and updates all docs pages to reflect the renderStack-centric API, host overlay descriptor model, serializable-props vs runtime-attachment boundary, and MobX control island guidance.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as MDV / Demo App
  participant SpatialCanvasViewerInner
  participant renderStackToLayerInputs
  participant resolveRenderStackHostLayers
  participant useSpatialCanvasRendererFromLayerInputs
  participant useLayerData

  Caller->>SpatialCanvasViewerInner: renderStack + hostLayerResolver + onFeatureHover/onFeatureClick
  SpatialCanvasViewerInner->>renderStackToLayerInputs: renderStack entries (spatial)
  renderStackToLayerInputs-->>SpatialCanvasViewerInner: { layers, layerOrder }
  SpatialCanvasViewerInner->>resolveRenderStackHostLayers: renderStack entries (host) + resolver
  resolveRenderStackHostLayers-->>SpatialCanvasViewerInner: hostDeckLayers[]
  SpatialCanvasViewerInner->>useSpatialCanvasRendererFromLayerInputs: layerInputs + hostDeckLayers + renderOrder
  useSpatialCanvasRendererFromLayerInputs->>useLayerData: spatialData + layerInputs
  useLayerData-->>useSpatialCanvasRendererFromLayerInputs: getFeaturePickEvent()
  useSpatialCanvasRendererFromLayerInputs-->>SpatialCanvasViewerInner: renderer (deckLayers, layerOrder, getFeaturePickEvent)
  SpatialCanvasViewerInner->>Caller: onFeatureHover(SpatialFeaturePickEvent)
  SpatialCanvasViewerInner->>Caller: onFeatureClick(SpatialFeaturePickEvent)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Taylor-CCB-Group/SpatialData.js#32: Introduced the headless SpatialCanvasViewer and original useSpatialCanvasRenderer hook that this PR refactors to the renderStack/hostLayerResolver model.
  • Taylor-CCB-Group/SpatialData.js#36: Overlaps in shapes pick/hover/click plumbing within SpatialCanvasViewer.tsx and useLayerData.ts, which this PR extends with the unified getFeaturePickEvent API.
  • Taylor-CCB-Group/SpatialData.js#30: Shares the same docs/docs/vis/mdv-integration.mdx surface for describing the headless externally-controlled SpatialCanvas layer composition model.

Poem

🐇 Hopping through the stack with care,
Each entry placed with id to spare,
spatial, host, and group align —
The render order falls in line.
No raw deck instances stored today,
Just descriptors to resolve and play!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main architectural changes: moving render stack ownership to layers and clarifying the property-to-layer flow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/clarify-propstolayers-flow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
packages/layers/tests/renderStack.spec.ts (1)

89-105: ⚡ Quick win

Add a regression test for duplicate entry.id rejection.

Current tests assert ordering but not uniqueness enforcement. Add one case that expects renderStackSchema.safeParse to fail when two entries share the same id.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/layers/tests/renderStack.spec.ts` around lines 89 - 105, The test
suite for renderStackSchema needs to add a regression test case that verifies
duplicate entry IDs are rejected by the schema validation. Add a new test that
creates a stack object with duplicate id values in the entries array and calls
renderStackSchema.safeParse() on it, then assert that the parsing fails (the
result should indicate validation errors, not success). This ensures the schema
enforces uniqueness constraints on entry IDs alongside the existing tests that
verify ordering behavior of getRenderStackEntryIds and
getRenderStackHostLayerIds.
packages/vis/demo/src/buildHeadlessLayers.ts (1)

49-49: ⚡ Quick win

Use the shared RenderStack schema version constant

Line 49 hardcodes schemaVersion: 1; this can drift from the canonical version and silently desync demo output from the RenderStack contract.

Suggested diff
-import type { RenderStack, RenderStackSpatialElementType } from '../../src/index';
+import {
+  RENDER_STACK_SCHEMA_VERSION,
+  type RenderStack,
+  type RenderStackSpatialElementType,
+} from '../../src/index';
...
-  return { schemaVersion: 1, entries };
+  return { schemaVersion: RENDER_STACK_SCHEMA_VERSION, entries };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vis/demo/src/buildHeadlessLayers.ts` at line 49, The return
statement in the buildHeadlessLayers function hardcodes schemaVersion as the
literal value 1, which can drift from the canonical RenderStack schema version
constant. Replace the hardcoded numeric value 1 with the shared RenderStack
schema version constant, and ensure that constant is imported at the top of the
file if it is not already.
packages/vis/tests/spatialCanvasViewer.spec.ts (1)

69-78: ⚡ Quick win

Add a regression test for host resolvers that return Layer[].

Current coverage only exercises single-layer host resolution. A case with one host entry resolving to multiple layers (and a spatial entry after it) would catch ordering regressions in adapter/sort integration.

Also applies to: 96-108

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vis/tests/spatialCanvasViewer.spec.ts` around lines 69 - 78, Add a
new test case after the existing 'resolves host descriptors into deck layers
with stack ids' test that specifically exercises host resolvers returning
multiple layers. Create a test where a single host entry in the
renderStackSchema resolves to multiple layers via the host resolver function,
and include a spatial entry after it in the stack configuration. Verify that the
resolved layers maintain proper ordering, with the multiple layers from the host
resolver appearing before the spatial layer. This will provide regression test
coverage for the adapter/sort integration when handling multi-layer host
resolution scenarios.
packages/vis/src/SpatialCanvas/renderStackAdapters.ts (1)

44-50: ⚡ Quick win

Replace as assertions with typed narrowing in the adapter boundary.

At Line 44-50, these casts are avoidable and weaken the type contract for this conversion path.

Refactor sketch
 case 'image': {
-  return { ...base, type: 'image' } as ImageLayerConfig;
+  const config: ImageLayerConfig = { ...base, type: 'image' };
+  return config;
 }
 case 'shapes': {
-  return { ...base, type: 'shapes' } as ShapesLayerConfig;
+  const config: ShapesLayerConfig = { ...base, type: 'shapes' };
+  return config;
 }
 case 'points': {
-  return { ...base, type: 'points' } as PointsLayerConfig;
+  const config: PointsLayerConfig = { ...base, type: 'points' };
+  return config;
 }
 case 'labels': {
-  return { ...base, type: 'labels' } as LabelsLayerConfig;
+  const config: LabelsLayerConfig = { ...base, type: 'labels' };
+  return config;
 }

As per coding guidelines, “Avoid type assertions (as); use satisfies, discriminated unions, and small helpers that return precise types.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vis/src/SpatialCanvas/renderStackAdapters.ts` around lines 44 - 50,
The switch statement in the adapter is using `as` type assertions to cast return
values for ImageLayerConfig, ShapesLayerConfig, PointsLayerConfig, and
LabelsLayerConfig, which weakens the type contract. Replace these assertions by
either creating separate typed helper functions for each case that naturally
return the correct layer config type, or restructure the switch cases to
leverage a discriminated union pattern where the type property itself provides
the necessary type narrowing to eliminate the need for assertions.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/layers/src/renderStack.ts`:
- Around line 56-59: The renderStackSchema does not validate that all entries in
the entries array have unique id values, which can cause issues with stable
ordering and host/layer mapping. Add a refinement to the renderStackSchema
object after defining the base schema that validates the entries array contains
no duplicate entry.id values by checking that the number of unique IDs equals
the total number of entries, using appropriate Zod refinement methods like
superRefine() to provide a clear error message when duplicate IDs are detected.
- Around line 15-31: The renderStackEntryBaseSchema and
renderStackSpatialEntrySchema (along with other object schemas in the file at
lines 35, 56 and the ranges 33-39, 41-48, 56-59) currently use non-strict
z.object() calls which silently accept and drop unknown keys, weakening contract
validation. Convert all these z.object() definitions to use strict parsing by
chaining .strict() after the object() call and its property definitions. This
ensures that any unknown properties in persisted RenderStack configurations are
rejected during validation rather than silently ignored, aligning with the
strict schema guideline.

In `@packages/vis/src/SpatialCanvas/renderStackAdapters.ts`:
- Around line 82-85: When a resolver returns multiple Layer instances
(Array.isArray(resolved) is true and compact.length > 1), those layers are
pushed without cloning and assigning the entry.id, which causes them to retain
unknown runtime ids. This breaks stack ordering because unknown ids are sorted
after ordered ids in the sorting logic at lines 102-117. Fix this by cloning
each layer in the compact array with the entry.id (similar to what's done in the
compact.length === 1 case), so all resolved layers maintain their declared
render-stack position regardless of whether the resolver returns a single layer
or multiple layers.

---

Nitpick comments:
In `@packages/layers/tests/renderStack.spec.ts`:
- Around line 89-105: The test suite for renderStackSchema needs to add a
regression test case that verifies duplicate entry IDs are rejected by the
schema validation. Add a new test that creates a stack object with duplicate id
values in the entries array and calls renderStackSchema.safeParse() on it, then
assert that the parsing fails (the result should indicate validation errors, not
success). This ensures the schema enforces uniqueness constraints on entry IDs
alongside the existing tests that verify ordering behavior of
getRenderStackEntryIds and getRenderStackHostLayerIds.

In `@packages/vis/demo/src/buildHeadlessLayers.ts`:
- Line 49: The return statement in the buildHeadlessLayers function hardcodes
schemaVersion as the literal value 1, which can drift from the canonical
RenderStack schema version constant. Replace the hardcoded numeric value 1 with
the shared RenderStack schema version constant, and ensure that constant is
imported at the top of the file if it is not already.

In `@packages/vis/src/SpatialCanvas/renderStackAdapters.ts`:
- Around line 44-50: The switch statement in the adapter is using `as` type
assertions to cast return values for ImageLayerConfig, ShapesLayerConfig,
PointsLayerConfig, and LabelsLayerConfig, which weakens the type contract.
Replace these assertions by either creating separate typed helper functions for
each case that naturally return the correct layer config type, or restructure
the switch cases to leverage a discriminated union pattern where the type
property itself provides the necessary type narrowing to eliminate the need for
assertions.

In `@packages/vis/tests/spatialCanvasViewer.spec.ts`:
- Around line 69-78: Add a new test case after the existing 'resolves host
descriptors into deck layers with stack ids' test that specifically exercises
host resolvers returning multiple layers. Create a test where a single host
entry in the renderStackSchema resolves to multiple layers via the host resolver
function, and include a spatial entry after it in the stack configuration.
Verify that the resolved layers maintain proper ordering, with the multiple
layers from the host resolver appearing before the spatial layer. This will
provide regression test coverage for the adapter/sort integration when handling
multi-layer host resolution scenarios.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a679e8fc-a862-4468-9920-478f3f2a0fa3

📥 Commits

Reviewing files that changed from the base of the PR and between e20648d and a6e4fa0.

📒 Files selected for processing (24)
  • CONTEXT.md
  • docs/adr/0001-render-stack-owned-by-layers.md
  • docs/docs/intro.mdx
  • docs/docs/layers/overview.mdx
  • docs/docs/vis/headless-viewer.mdx
  • docs/docs/vis/layer-prop-flow.mdx
  • docs/docs/vis/mdv-integration.mdx
  • docs/docs/vis/mdv-release-checklist.mdx
  • docs/docs/vis/overview.mdx
  • packages/layers/src/index.ts
  • packages/layers/src/renderStack.ts
  • packages/layers/tests/renderStack.spec.ts
  • packages/vis/demo/src/CodecFixtureDemo.tsx
  • packages/vis/demo/src/HeadlessBlobsDemo.tsx
  • packages/vis/demo/src/buildHeadlessLayers.ts
  • packages/vis/demo/tsconfig.json
  • packages/vis/src/SpatialCanvas/SpatialCanvasViewer.tsx
  • packages/vis/src/SpatialCanvas/VivSpatialViewer.tsx
  • packages/vis/src/SpatialCanvas/index.tsx
  • packages/vis/src/SpatialCanvas/public.ts
  • packages/vis/src/SpatialCanvas/renderStackAdapters.ts
  • packages/vis/src/SpatialCanvas/useLayerData.ts
  • packages/vis/src/index.ts
  • packages/vis/tests/spatialCanvasViewer.spec.ts

Comment thread packages/layers/src/renderStack.ts Outdated
Comment thread packages/layers/src/renderStack.ts Outdated
Comment thread packages/vis/src/SpatialCanvas/renderStackAdapters.ts
validate unique layer id.
avoid potential issue with stack-order breaking by always cloning layers (hopefully not too garbage-intensive).
@xinaesthete
xinaesthete merged commit 7c7fdf6 into main Jun 19, 2026
2 checks passed
@xinaesthete
xinaesthete deleted the codex/clarify-propstolayers-flow branch June 19, 2026 09:53
@github-actions github-actions Bot mentioned this pull request Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant