Skip to content

Consume Resource Resolvers in useLayerData (ADR 0004 Step 1) - #86

Merged
xinaesthete merged 6 commits into
mainfrom
claude/resource-resolver-adr-88a5c8
Jul 16, 2026
Merged

Consume Resource Resolvers in useLayerData (ADR 0004 Step 1)#86
xinaesthete merged 6 commits into
mainfrom
claude/resource-resolver-adr-88a5c8

Conversation

@xinaesthete

@xinaesthete xinaesthete commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Completes Step 1 of the Resource Resolver work (ADR 0004): useLayerData now
consumes the per-kind resolvers that #85 landed unconsumed, replacing the ~400-line
Promise.all kind-switch with a single SpatialEntryStore.reconcile() loop. This is
the "fork point" the handoff calls out — after it lands, Track A (points) and Track B
(shapes) touch mostly different files.

Executed per docs/plans/step1-consumption-tactics.md. The 17-member public surface
is unchanged and guarded by useLayerData.spec.tsx throughout.

What changed

Increment 1 — shapes. ShapesResolver drives geometry/tooltip/fill-colour-row
loads. Vis-side projection memos handle the two couplings the plan flags: the
tooltip→geometry rowIndexByFeatureIndex patch (identity-stable so deck doesn't tear
down per frame), and keeping the physical-size world-bounds compute in the hook.

Increment 2 — images + labels. ImagesResolver / LabelsResolver consumed via
getLoadedData. LabelsLoaderData retyped to the resolver's LabelsChannelDefaults
(tooltip is now a separate resolver resource). ImageLoaderData (public API)
unchanged.

Increment 3 — fold points + the store. One SpatialEntryStore + one reconcile()
commit-effect replace the per-kind driving effects (the hook now has two useEffects
total). Points stays owned by the stable PointsDataEngine the panels subscribe to;
the store borrows it through a non-owning proxy (createNonOwningResolver, no-op
dispose) so a dataset-swap rebuild of the store doesn't clear the engine's cache.

Docs + decomposition. Expanded the lifecycle rationale for the proxy; refreshed
the resolver handoff and the public SpatialCanvas status page (both had stale status
claims); added a changeset. Finally, an intermediate extraction moved the proxy and
the shapes projection helpers into resolvers/nonOwningResolver.ts and
shapesProjection.ts, dropping the hook from 1873 → 1446 lines.

Reviewer notes

  • The non-owning points proxy is the one deliberate exception to the store's
    ownership model
    — worth the closest look. Rationale (two-owner problem, why the
    no-op is correct not just safe, alternatives rejected, exit condition) is documented
    in resolvers/nonOwningResolver.ts, and guarded by a test that fails if it ever
    regresses to a real dispose (points cache must survive a spatialData swap).
  • Out of scope by design: the render-phase pointsEngine.ensureMatchingFeaturesLoaded
    / ensureRowFeatureCodes calls in getLayers stay put (they migrate into plan()
    under Track A), so the points reconcile context carries only the memory cap.
  • One regression caught during review, not by a test: the shapes fill-colour entry
    must not be created until rows load, or the feature-state runtime never rebuilds and
    fill colours never appear. Fixed; noted in the plan doc.
  • Test-coverage gap: the guard test covers the shapes + points lifecycle, resource
    identity, and the surface — but not shape fill-by-column, image/labels rendering, or
    tooltips. Those were verified by reading. Worth adding before Track A/B build on this.
  • c6234e3 (disable the tsgo VS Code extension) is an incidental workspace-config
    commit picked up on this branch, unrelated to the resolver work.

Verification

tsc clean, Biome CI gate (packages/*/src) clean, full suite pnpm -r --filter='!docs' test
= 550 passing, pnpm --filter @spatialdata/vis build succeeds. (Both core and
layers need a local build first — vis typechecks against their dist.)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Refreshed SpatialCanvas layer loading to use a resolver-driven flow for shapes, images, labels, and points.
    • Preserves point-layer render resources across dataset swaps.
    • Improved shapes styling (fill-color defaults/signatures), hidden-feature handling, tooltips, picking, and feature-state behavior.
    • Enhanced world-bound calculations and progressive point rendering behavior.
  • Documentation

    • Updated rendering status, implementation progress, and step-by-step notes for resolver consumption and remaining roadmap items.
  • Tests

    • Added coverage to ensure points layer resources remain stable when switching datasets.
  • Chores

    • Updated workspace TypeScript/JavaScript settings.

xinaesthete and others added 5 commits July 15, 2026 14:54
Make useLayerData *consume* the resolvers #85 landed unconsumed, per
docs/plans/step1-consumption-tactics.md. The 17-member public surface is
unchanged; useLayerData.spec.tsx stays green throughout. Net -724/+394 lines.

- Inc 1 (shapes): ShapesResolver drives geometry/tooltip/fill-colour-row loads;
  vis-side projection memos handle the tooltip->geometry patch (coupling #1) and
  keep prebuilt/fill-colour lazy. Fill-colour entry is withheld until rows load so
  the feature-state runtime rebuilds and fill colours actually appear.
- Inc 2 (images + labels): ImagesResolver/LabelsResolver consumed via getLoadedData;
  LabelsLoaderData retyped to LabelsChannelDefaults (tooltip is now a separate
  resource). Physical-size world-bounds compute kept in the hook (coupling #2).
- Inc 3 (store): one SpatialEntryStore + one reconcile() commit-effect replace the
  per-kind driving effects (hook now has two useEffects total). Points is wrapped in
  a non-owning proxy so the stable PointsDataEngine the panels subscribe to survives
  a store rebuild on dataset swap; points row-codes/matching stay on the render-phase
  engine calls in getLayers (Track A).

Verified: vis typecheck + build clean, full suite 549 passing, Biome gate clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The store is designed to own its resolvers (subscribe + dispose), but points is
owned by the stable PointsDataEngine the panels subscribe to — so the store borrows
it through createNonOwningResolver (no-op dispose) to avoid clearing the engine's
cache on a dataset-swap rebuild. Expand the rationale where it goes against the
store's ownership grain: the two-owner problem, why the no-op is correct not just
safe, why it doesn't reintroduce "points is special" in the store, alternatives
rejected, and the exit condition. Add the ownership model at the construction site
and note the StrictMode useMemo-subscribe caveat.

Add a lifecycle test: a spatialData swap rebuilds the raster resolvers and the store,
and the points cache/render-resource identity must survive it — the test that fails
if the proxy ever regresses to a real dispose.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- resource-resolver-handoff.md: flip Status off "ready for implementation", add a
  Progress section and mark Step 0/1 landed (contracts, four resolvers, useLayerData
  consumption via SpatialEntryStore.reconcile); note the non-owning points proxy and
  the deferred render-phase points calls (Track A).
- spatial-canvas-status.mdx: replace the stale "minimal ScatterplotLayer" points
  description with the PointsDataEngine reality; retitle useLayerData; add a
  Resource Resolver entry to "Recently landed"; refresh the feature/table roadmap
  item (tooltip/pick routing done, ping-pong remains).
- Add a changeset for the vis-side resolver consumption.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intermediate decomposition pass, no behaviour change. Moves two cohesive,
kind-local chunks out of the 1.6kloc hook:

- resolvers/nonOwningResolver.ts — `createNonOwningResolver` + its lifecycle
  rationale (the store-ownership exception for points).
- shapesProjection.ts — the shapes feature-state / fill-colour projection helpers
  and their cache-entry types (`ShapePrebuiltEntry`, `ShapeFillColorEntry`,
  `getStableShapeFeatureStateRuntime`, signature/serialise helpers). This is the
  `project()` half of ADR 0004 §4; a vis-local waypoint before Step 3 relocates it
  into @spatialdata/layers.

useLayerData.ts drops from 1623 to 1446 lines and imports both. The shapes read
path now lives in a small dedicated module, so Track B / Step 3 touch it rather than
the hook. Behaviour-preserving: control-char signature separators kept byte-identical;
full suite (550 tests) green, vis typecheck + build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 27110ede-a8a2-4bc9-a94f-a16edecc84d5

📥 Commits

Reviewing files that changed from the base of the PR and between 187cb7f and d110fc0.

📒 Files selected for processing (2)
  • packages/vis/src/SpatialCanvas/shapesProjection.ts
  • packages/vis/src/SpatialCanvas/useLayerData.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/vis/src/SpatialCanvas/shapesProjection.ts
  • packages/vis/src/SpatialCanvas/useLayerData.ts

📝 Walkthrough

Walkthrough

useLayerData now loads visible layers through a single SpatialEntryStore.reconcile() loop and per-kind resolvers. Shapes use dedicated projection caches, while image, label, tooltip, pick, bounds, and points paths read resolver or engine state. Tests cover points resource preservation across dataset swaps.

Changes

Resolver consumption and rendering integration

Layer / File(s) Summary
Store reconciliation and resolver lifecycle
packages/vis/src/SpatialCanvas/useLayerData.ts, packages/vis/src/SpatialCanvas/resolvers/nonOwningResolver.ts, packages/vis/tests/useLayerData.spec.tsx
useLayerData reconciles visible contexts through SpatialEntryStore, preserves the points engine with a non-owning resolver, reads resolver caches for readiness and eviction, and tests resource identity across dataset swaps.
Shapes projection and stable feature state
packages/vis/src/SpatialCanvas/shapesProjection.ts, packages/vis/src/SpatialCanvas/useLayerData.ts
Shapes rendering uses dedicated fill-color, feature-state, prebuilt-data, and signature-based projection helpers with memoized runtime reuse.
Resolver-backed render and interaction paths
packages/vis/src/SpatialCanvas/useLayerData.ts
World bounds, image and label rendering, loaded-data getters, tooltip metadata, pick-event row indices, and image props now use resolver outputs.
Implementation records and validation
.changeset/consume-resource-resolvers.md, docs/plans/*, docs/docs/vis/spatial-canvas-status.mdx, .vscode/settings.json
Changesets, planning/status documents, and workspace settings record the landed resolver-consumption steps and updated points and tooltip/pick architecture.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant useLayerData
  participant SpatialEntryStore
  participant ResourceResolvers
  participant PointsDataEngine
  useLayerData->>SpatialEntryStore: reconcile visible layer contexts
  SpatialEntryStore->>ResourceResolvers: plan and load layer resources
  ResourceResolvers->>PointsDataEngine: access points through non-owning resolver
  SpatialEntryStore-->>useLayerData: notify cache updates
  useLayerData->>ResourceResolvers: read render and tooltip metadata
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% 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 concisely summarizes the main change: moving useLayerData to resource resolver consumption for ADR 0004 Step 1.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/resource-resolver-adr-88a5c8

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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.

@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: 2

🤖 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/vis/src/SpatialCanvas/shapesProjection.ts`:
- Around line 71-100: Preserve explicit strokeColorByFeatureId values in
mergeShapeFeatureStateForRender when fillColorByColumn is active instead of
unconditionally replacing them with the fill map. If outlines should mirror fill
colors only when no stroke override exists, apply that fallback and add a brief
comment; update getShapeFeatureStateSignature to stop hashing
strokeColorByFeatureId in that mirroring case.

In `@packages/vis/src/SpatialCanvas/useLayerData.ts`:
- Around line 533-590: The reconciliation effect does not rerun when element
resolution or eviction changes elementMap. Update reloadElement to trigger
store.reconcile with the current visible layer contexts, or introduce an
equivalent elementMap-driven trigger, ensuring shapes, images, and labels are
replanned after reloads and previously missing elements become available.
🪄 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: ddecf903-fef6-4969-b1d6-ef27c413f624

📥 Commits

Reviewing files that changed from the base of the PR and between b53c91b and 187cb7f.

📒 Files selected for processing (9)
  • .changeset/consume-resource-resolvers.md
  • .vscode/settings.json
  • docs/docs/vis/spatial-canvas-status.mdx
  • docs/plans/resource-resolver-handoff.md
  • docs/plans/step1-consumption-tactics.md
  • packages/vis/src/SpatialCanvas/resolvers/nonOwningResolver.ts
  • packages/vis/src/SpatialCanvas/shapesProjection.ts
  • packages/vis/src/SpatialCanvas/useLayerData.ts
  • packages/vis/tests/useLayerData.spec.tsx

Comment thread packages/vis/src/SpatialCanvas/shapesProjection.ts
Comment thread packages/vis/src/SpatialCanvas/useLayerData.ts Outdated
…hange

Two code-review findings, verified against current code:

- shapesProjection: mergeShapeFeatureStateForRender no longer clobbers an explicit
  per-feature strokeColorByFeatureId when a fill-by-column encoding is active. It now
  mirrors the fill map only when the caller has NOT set an explicit stroke override
  (the schema allows both together, e.g. via SpatialLayerProps). Signature unchanged
  and deliberately so: it already hashes the explicit stroke, which is what drives the
  render; dropping that term would stale the runtime when the stroke changes, and in
  the mirroring case the term is already empty.
- useLayerData: the reconcile effect now depends on elementMapValue, so it replans
  when element resolution changes without layers/store changing — e.g. a coordinate
  system switch that makes a previously unavailable element resolvable. The map is
  memoised on availableElements, so no per-render churn.

Skipped: wiring reconcile into reloadElement (the finding's other suggestion) —
reloadElement has zero runtime callers (dead surface, per the Step 3 punchlist), so it
would fix nothing observable.

Verified: vis typecheck + build clean, Biome gate clean, full suite 550 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@xinaesthete
xinaesthete merged commit 716bc44 into main Jul 16, 2026
4 checks passed
@xinaesthete
xinaesthete deleted the claude/resource-resolver-adr-88a5c8 branch July 16, 2026 15:17
@github-actions github-actions Bot mentioned this pull request Jul 16, 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