Skip to content

feat(map): styleEpoch readiness and private useMapLayers - #69

Open
km-git007 wants to merge 11 commits into
AnmolSaini16:mainfrom
km-git007:feat/style-epoch-usemaplayers
Open

feat(map): styleEpoch readiness and private useMapLayers#69
km-git007 wants to merge 11 commits into
AnmolSaini16:mainfrom
km-git007:feat/style-epoch-usemaplayers

Conversation

@km-git007

@km-git007 km-git007 commented Jul 17, 2026

Copy link
Copy Markdown

Goal

Make theme/style swaps reliable for declarative map layers.

One readiness owner (styleEpoch) plus one private layer lifecycle (useMapLayers), so Route / GeoJSON / Arc / Cluster recreate on the new style instead of relying on style.load + pendingStyle remount tricks or copy-pasted MapLibre source/layer code.

Why this PR exists

This work was originally planned as three smaller PRs that together would reach the same end state.

PR #68 was the first slice (readiness / styleEpoch only). It did not land as intended: after miscommunication, the timeout issue was treated as fixed by the upstream style.load / pendingStyle path (35e328a), so the styleEpoch foundation never shipped.

This PR ships the full intended end state in one place so the architecture is reviewable as a whole.

What changed

  • Readiness: subscribeStyleReady owns style-ready. Context exposes styleEpoch (bumps once per ready style). isLoaded is the safe-to-touch gate and closes during in-flight setStyle.
  • Lifecycle: private useMapLayers owns GeoJSON source/layer create-teardown, setData, and paint/layout sync. Shells keep transforms + semantic events only.
  • Theme swaps: layers recreate when styleEpoch advances. setStyle uses { diff: false } (with an inline note) after { diff: true } hung without a reliable post-arm styledata.
  • Docs + registry: advanced-usage / api-reference updated; public/r/map.json regenerated.
  • Size: src/registry/map.tsx is smaller than current main (2175 vs 2225).

Out of scope

Exporting useMapLayers, MapHeatmap, Marker/Controls refactors, popup module, nav/block catalog work.

Test plan

  • Theme toggle on /docs/routes, /docs/geojson, /docs/arcs, /docs/clusters (layers return; no missing source/layer errors)
  • Rapid theme toggles (epoch bumps once per completed style; no listener leaks)
  • blank GeoJSON + cluster click zoom
  • tsc --noEmit + lint on touched files
  • public/r/map.json has styleEpoch; no pendingStyle / permanent style.load readiness owner
  • wc -l src/registry/map.tsx below main

Summary by CodeRabbit

  • Bug Fixes

    • Improved map readiness tracking across theme and basemap style changes, including a new styleEpoch readiness signal for safer layer setup.
    • Ensured GeoJSON, route, arc, and cluster layers correctly recreate and keep interactions working after style reloads.
    • Enhanced hover highlighting behavior with more reliable paint updates, cursor cleanup, and hover state handling.
  • Documentation

    • Updated useMap docs and examples to include styleEpoch and explain when to use it.
  • Chores

    • Updated .gitignore to ignore local git worktrees (.worktrees/).

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

@km-git007 is attempting to deploy a commit to the mapcn oss program Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The map lifecycle now gates interaction readiness on map load and style readiness, exposes styleEpoch through useMap(), and centralizes source/layer management across map visualizations. Documentation and embedded output describe the new lifecycle. Local Git worktrees are ignored.

Changes

Map lifecycle and layers

Layer / File(s) Summary
Style readiness and context
src/registry/map.tsx
Style readiness uses styledata subscriptions and isStyleLoaded() checks; style replacement resets readiness, and styleEpoch is exposed through useMap().
Shared source and layer lifecycle
src/registry/map.tsx
useMapLayers manages sources, layers, updates, teardown, paint/layout synchronization, hover state, and interaction rebinding for route, GeoJSON, arc, and cluster components.
Public documentation and generated map output
src/app/(main)/docs/..., public/r/map.json
Documentation and embedded output describe styleEpoch, style-driven layer recreation, and updated readiness semantics.

Repository hygiene

Layer / File(s) Summary
Ignore local worktrees
.gitignore
Adds .worktrees/ to the ignored paths.

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

Sequence Diagram(s)

sequenceDiagram
  participant Map
  participant subscribeStyleReady
  participant MapLibre
  participant useMapLayers
  Map->>subscribeStyleReady: register style-ready callback
  MapLibre-->>subscribeStyleReady: emit styledata and report isStyleLoaded()
  subscribeStyleReady->>Map: invoke handleStyleReady
  Map->>Map: increment styleEpoch
  Map->>useMapLayers: render layers after readiness
  useMapLayers->>MapLibre: add or recreate sources and layers
Loading

Possibly related PRs

  • AnmolSaini16/mapcn#44: Updates the theming and style-resolution flow that this change extends with style readiness and styleEpoch.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% 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 accurately summarizes the main map readiness and private layer-lifecycle changes in the pull request.
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 unit tests (beta)
  • Create PR with unit tests

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.

Key declarative layers on styleEpoch so theme style swaps recreate
sources through one shared lifecycle hook, not style.load/pendingStyle.
@km-git007
km-git007 force-pushed the feat/style-epoch-usemaplayers branch from 31ce67e to 457a040 Compare July 17, 2026 12:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@src/registry/map.tsx`:
- Around line 309-310: Move the projectionRef.current assignment out of the
render path and synchronize it in a useEffect tied to projection, so
handleStyleReady only observes committed projection values.
- Around line 1604-1609: Update handleMouseMove so hover deduplication only runs
when feature.id is non-null; allow features without IDs to continue reaching
onHover on every mouse move, while preserving lastFeatureId tracking and
deduplication for valid IDs.
- Around line 1259-1274: Update the layer synchronization useEffect to clear
paint and layout properties that existed in the previous layer configuration but
are absent from the current layer.paint or layer.layout. Track the prior key
sets per layer, reset removed properties appropriately before applying current
values, and preserve the existing isLoaded, map, and layer guards.
- Around line 433-441: Update the style replacement flow around setStyle and
handleStyleReady to clear isStyleReady before invoking mapInstance.setStyle,
while preserving isLoaded. Restore isStyleReady from handleStyleReady once the
new style is ready so useMapLayers and other style-sensitive effects remain
paused during replacement.
🪄 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: 9c3ad47b-11d5-4ec8-a22c-d9c4a906eb35

📥 Commits

Reviewing files that changed from the base of the PR and between be43eac and 31ce67e.

📒 Files selected for processing (5)
  • .gitignore
  • public/r/map.json
  • src/app/(main)/docs/advanced-usage/page.tsx
  • src/app/(main)/docs/api-reference/page.tsx
  • src/registry/map.tsx

Comment thread src/registry/map.tsx Outdated
Comment thread src/registry/map.tsx
Comment thread src/registry/map.tsx Outdated
Comment thread src/registry/map.tsx
Close the ready gate during style swaps, omit invalid route dasharrays,
and keep shell listeners keyed to the same lifecycle revision.
Keep styleEpoch readiness and useMapLayers; bring in data-theme
detection, public prop type exports, and homepage/style cleanups.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/app/(main)/docs/advanced-usage/page.tsx (1)

49-88: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Ensure the documentation example is valid TypeScript.

As per coding guidelines, "Write concise, technical TypeScript code with accurate examples". The useMapCode snippet has implicit any parameters, an untyped prop, and raw JSX at the root which references an undefined variable parks. Adding basic type annotations and wrapping the usage in a default export component ensures the example is valid TypeScript and copy-paste friendly.

♻️ Proposed refactor
-    const handleClick = (e) => {
+    const handleClick = (e: any) => {
       console.log("Clicked at:", e.lngLat);
     };
 
     map.on("click", handleClick);
     return () => map.off("click", handleClick);
   }, [map, isLoaded]);
 
   return null;
 }
 
 // Custom sources/layers must recreate when styleEpoch advances.
-function CustomParkLayer({ data }) {
+function CustomParkLayer({ data }: { data: GeoJSON.FeatureCollection }) {
   const { map, isLoaded, styleEpoch } = useMap();
 
   useEffect(() => {
     if (!map || !isLoaded) return;
 
     map.addSource("parks", { type: "geojson", data });
     map.addLayer({
       id: "parks-fill",
       type: "fill",
       source: "parks",
       paint: { "fill-color": "`#22c55e`", "fill-opacity": 0.4 },
     });
 
     return () => {
       if (map.getLayer("parks-fill")) map.removeLayer("parks-fill");
       if (map.getSource("parks")) map.removeSource("parks");
     };
   }, [map, isLoaded, styleEpoch, data]);
 
   return null;
 }
 
 // Usage
-<Map center={[-74, 40.7]} zoom={10}>
-  <MapEventListener />
-  <CustomParkLayer data={parks} />
-</Map>`;
+export default function App() {
+  return (
+    <Map center={[-74, 40.7]} zoom={10}>
+      <MapEventListener />
+      <CustomParkLayer data={{ type: "FeatureCollection", features: [] }} />
+    </Map>
+  );
+}`;
🤖 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 `@src/app/`(main)/docs/advanced-usage/page.tsx around lines 49 - 88, Update the
useMapCode documentation example to use explicit TypeScript types for the click
event, map-layer props, and data, then wrap the usage JSX in a default-exported
component that defines or receives parks instead of referencing an undefined
root variable. Keep the existing MapEventListener and CustomParkLayer behavior
unchanged while making the snippet compile and remain copy-paste friendly.

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.

Nitpick comments:
In `@src/app/`(main)/docs/advanced-usage/page.tsx:
- Around line 49-88: Update the useMapCode documentation example to use explicit
TypeScript types for the click event, map-layer props, and data, then wrap the
usage JSX in a default-exported component that defines or receives parks instead
of referencing an undefined root variable. Keep the existing MapEventListener
and CustomParkLayer behavior unchanged while making the snippet compile and
remain copy-paste friendly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d6356e4e-d3bb-48e5-aef7-a5410d69d16d

📥 Commits

Reviewing files that changed from the base of the PR and between 31ce67e and 9d52233.

📒 Files selected for processing (5)
  • .gitignore
  • public/r/map.json
  • src/app/(main)/docs/advanced-usage/page.tsx
  • src/app/(main)/docs/api-reference/page.tsx
  • src/registry/map.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/app/(main)/docs/api-reference/page.tsx
  • public/r/map.json
  • src/registry/map.tsx

Drop redundant lifecycle helpers and leaner layer create/sync paths
without touching the proven style readiness owner.
@km-git007

km-git007 commented Jul 17, 2026

Copy link
Copy Markdown
Author

Good nit. Typed the advanced-usage useMap snippet (MapMouseEvent, FeatureCollection) and wrapped the usage in an Example component so parks is a real prop.

Make the styleEpoch docs snippet valid TypeScript so it is safer to copy.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/registry/map.tsx (1)

108-116: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fall back to the system theme when the explicit theme is removed.

When class or data-theme is cleared, docTheme becomes null and the previous theme remains stale.

Proposed fix
 const observer = new MutationObserver(() => {
   const docTheme = getDocumentTheme();
-  if (docTheme) {
-    setDetectedTheme(docTheme);
-  }
+  setDetectedTheme(docTheme ?? getSystemTheme());
 });
🤖 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 `@src/registry/map.tsx` around lines 108 - 116, Update the MutationObserver
callback in the theme detection flow to handle a null result from
getDocumentTheme(): setDetectedTheme to the system/default theme when class or
data-theme is removed, while preserving the explicit docTheme path when present.
🤖 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 `@public/r/map.json`:
- Line 17: The Map provider currently mounts children whenever mapInstance
exists, before the combined readiness gate is satisfied. Update the children
render in Map to require isLoaded (hasLoadEvent && isStyleReady) alongside
mapInstance, so consumers mount only after map and style readiness and unmount
during style replacement.
- Line 17: The generated map artifact still contains hardcoded colored and RGBA
paint defaults. Update the source symbols DEFAULT_ARC_PAINT,
GEOJSON_DEFAULT_COLORS, DEFAULT_CLUSTER_COLORS, pointColor defaults, and
blankMapStyle to use semantic grayscale CSS theme tokens from globals.css via
MapLibre-compatible color values, then regenerate public/r/map.json so its
embedded content matches the compliant source.
- Line 17: Update the hover-state lifecycle in useMapLayers so hoveredId cannot
persist across GeoJSON source rebuilds triggered by MapGeoJSON changes such as
promoteId or topology updates. Include the source-rebuild dependency (for
example, topologyKey) in the hover effect’s dependency handling and clear any
active feature state during cleanup before the replacement source is created.

In `@src/registry/map.tsx`:
- Line 1530: The event-handler refs are mutated during render and can expose
values from discarded renders. In src/registry/map.tsx:1530, move the
latestRef.current update for { onClick, onHover } into an effect; likewise, in
src/registry/map.tsx:1807, move the corresponding { data, onClick, onHover }
update into an effect so both refs synchronize only after commit.

---

Outside diff comments:
In `@src/registry/map.tsx`:
- Around line 108-116: Update the MutationObserver callback in the theme
detection flow to handle a null result from getDocumentTheme(): setDetectedTheme
to the system/default theme when class or data-theme is removed, while
preserving the explicit docTheme path when present.
🪄 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: f55c2457-8234-47da-a71b-cefafe682665

📥 Commits

Reviewing files that changed from the base of the PR and between 9d52233 and f4c937c.

📒 Files selected for processing (3)
  • public/r/map.json
  • src/app/(main)/docs/advanced-usage/page.tsx
  • src/registry/map.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/(main)/docs/advanced-usage/page.tsx

Comment thread public/r/map.json Outdated
Comment thread src/registry/map.tsx Outdated
Mount map children only when style-ready, sync handler refs after
commit, and fall back to system theme when the document theme clears.
Bind GeoJSON/Arc handlers from effect closures like MapRoute so we do
not need a render-time ref write or a syncing useEffect.
Bring in control transition and animation timing tweaks from main.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@public/r/map.json`:
- Line 17: Update mergeHoverPaint so every hoverPaint override is represented by
a feature-state case expression, including keys absent from the base paint; use
a valid non-hover fallback for those keys rather than assigning hoverValue
directly. Ensure fillHoverPaint and MapArc hoverPaint properties such as
fill-opacity remain unchanged for non-hovered features.
🪄 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: 4a487e5a-9f26-4049-a540-48e41d1304ce

📥 Commits

Reviewing files that changed from the base of the PR and between f4c937c and 547ad26.

📒 Files selected for processing (2)
  • public/r/map.json
  • src/registry/map.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/registry/map.tsx

Comment thread public/r/map.json Outdated
Always wrap hoverPaint overrides in a case expression so keys absent
from the base paint do not apply to every feature.
@km-git007

Copy link
Copy Markdown
Author

Hey @AnmolSaini16, when you get a chance could you take a look at this PR?

It covers the styleEpoch / theme-swap layer work we talked about earlier (the full end state after #68). Happy to walk through anything or tweak based on feedback.

@km-git007

Copy link
Copy Markdown
Author

Added one narrow follow-up after the resolved review: hover props now expose only MapLibre paint keys that support feature-state, the heuristic fallback is gone, and less-common hover keys require an explicit base paint value. Existing styleEpoch/useMapLayers work and history are unchanged. Registry, touched lint, TypeScript, build, MapLibre style validation, and browser hover/theme checks pass.

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