Skip to content

fix(map): restore boosted-marker clustering below zoom 6#1055

Merged
escapedcat merged 2 commits into
mainfrom
fix/boosted-marker-low-zoom-clustering
Jun 11, 2026
Merged

fix(map): restore boosted-marker clustering below zoom 6#1055
escapedcat merged 2 commits into
mainfrom
fix/boosted-marker-low-zoom-clustering

Conversation

@escapedcat

@escapedcat escapedcat commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

The Leaflet -> MapLibre migration (#1003) routed every boosted place to a permanent cluster:false source, so orange boosted pins never grouped at any zoom. The legacy /map clustered them together with regular markers at zoom <= BOOSTED_CLUSTERING_MAX_ZOOM (5) and only pulled them into a standalone layer above it, keeping the zoomed-out world view uncluttered.

Reintroduce that zoom gate: route boosted places into the clustered places source at/below zoom 5 and into the non-clustered places-boosted source above it, re-syncing on the boundary crossing — the MapLibre analogue of the old boostedLayer swap. Routing is extracted to $lib/map/boostedClustering with unit tests.

Summary by CodeRabbit

  • New Features

    • Boosted places now switch between clustered and standalone display based on zoom band, reducing visual churn during fractional zooms.
  • Bug Fixes

    • More accurate visibility for the selected merchant pulse on the map.
    • Fewer unnecessary map data refreshes when zooming within the same integer band.
  • Tests

    • Added tests covering boosted-place routing and zoom-based clustering behavior.

The Leaflet -> MapLibre migration (#1003) routed every boosted place to a permanent cluster:false source, so orange boosted pins never grouped at any zoom. The legacy /map clustered them together with regular markers at zoom <= BOOSTED_CLUSTERING_MAX_ZOOM (5) and only pulled them into a standalone layer above it, keeping the zoomed-out world view uncluttered.

Reintroduce that zoom gate: route boosted places into the clustered `places` source at/below zoom 5 and into the non-clustered `places-boosted` source above it, re-syncing on the boundary crossing — the MapLibre analogue of the old boostedLayer swap. Routing is extracted to $lib/map/boostedClustering with unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@netlify

netlify Bot commented Jun 11, 2026

Copy link
Copy Markdown

Deploy Preview for btcmap ready!

Name Link
🔨 Latest commit 4507099
🔍 Latest deploy log https://app.netlify.com/projects/btcmap/deploys/6a2aef90be545d00076102c3
😎 Deploy Preview https://deploy-preview-1055--btcmap.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 56 (🔴 down 30 from production)
Accessibility: 97 (no change from production)
Best Practices: 92 (🔴 down 8 from production)
SEO: 96 (no change from production)
PWA: 90 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

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: 835b5f59-96ee-4130-b911-2e402d3edbbe

📥 Commits

Reviewing files that changed from the base of the PR and between d9adb91 and 4507099.

📒 Files selected for processing (2)
  • src/lib/map/boostedClustering.test.ts
  • src/lib/map/boostedClustering.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/map/boostedClustering.ts
  • src/lib/map/boostedClustering.test.ts

📝 Walkthrough

Walkthrough

This PR introduces zoom-dependent routing for boosted places on a MapLibre map. New utilities partition places into clustered vs. standalone arrays based on zoom level and boost status, with comprehensive tests. The map page integrates these helpers, adds state tracking for re-sync optimization, and updates pulse and feature rendering logic.

Changes

Boosted Place Zoom-Based Routing

Layer / File(s) Summary
Boosted clustering routing utilities
src/lib/map/boostedClustering.ts
shouldClusterBoostedAtZoom() gates boosted clustering by zoom threshold; routePlacesByBoostAndZoom() partitions places into clustered and standalone arrays based on zoom and boost expiration; BoostedPlaceRouting type describes the output.
Routing logic tests
src/lib/map/boostedClustering.test.ts
Vitest suite verifying threshold behavior, correct routing of boosted/regular places, expiration handling, and deletion filtering across zoom levels.
Map page integration
src/routes/map/+page.svelte
Integrates routing helpers: imports them, adds state tracking (lastSyncedList, boostedAreClustered), updates pulse visibility to use shouldClusterBoostedAtZoom(), refactors feature property mapping and source syncing to use the routing logic, and adds a moveend guard that re-syncs only when the clustering boundary flips.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • dadofsambonzuki

Poem

A rabbit hops through zoom levels true,
Boosted places fold or stand in view,
Clustered low, gleaming alone up high,
GeoJSON footprints beneath the sky,
🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'fix(map): restore boosted-marker clustering below zoom 6' accurately describes the main change: re-enabling zoom-gated routing for boosted markers that clusters them below zoom 6.
Description check ✅ Passed The PR description covers the issue (clustering regression from migration), the solution (reintroduced zoom gate routing), and implementation details (extraction to library with tests), but omits some template sections like screenshots and related issue reference.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/boosted-marker-low-zoom-clustering

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

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/routes/map/+page.svelte (1)

228-242: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Pulse visibility skips existence checks for boosted features in places-boosted.

When the selected place is boosted and above the clustering threshold, visibility defaults to true without checking whether that feature is actually present in the places-boosted source. This can leave an orphan pulse when the place is filtered out.

💡 Proposed fix
 const updatePulseVisibility = () => {
 	if (!map || !pulseMarker || pulsePinId === null) return;
 	const place = get(placesById).get(pulsePinId);
-	let visible = true;
+	if (!place) {
+		pulseMarker.getElement().style.display = "none";
+		return;
+	}
+	let visible = true;
 	const inClusteredSource =
-		!!place && (!isBoosted(place) || shouldClusterBoostedAtZoom(map.getZoom()));
+		!isBoosted(place) || shouldClusterBoostedAtZoom(map.getZoom());
 	if (inClusteredSource) {
 		const filter: FilterSpecification = [
 			"all",
 			["!", ["has", "point_count"]],
 			["==", ["get", "id"], pulsePinId],
 		];
 		visible = map.querySourceFeatures("places", { filter }).length > 0;
+	} else {
+		const filter: FilterSpecification = ["==", ["get", "id"], pulsePinId];
+		visible = map.querySourceFeatures("places-boosted", { filter }).length > 0;
 	}
 	pulseMarker.getElement().style.display = visible ? "" : "none";
 };
🤖 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/routes/map/`+page.svelte around lines 228 - 242, updatePulseVisibility
currently assumes boosted features exist when isBoosted(place) &&
shouldClusterBoostedAtZoom(map.getZoom()), causing an orphan pulse; update the
function (updatePulseVisibility) to query the correct source when the place is
boosted and above threshold (use "places-boosted" instead of "places") by
constructing the same filter (["all", ["!", ["has","point_count"]], ["==",
["get","id"], pulsePinId]]) and setting visible based on
map.querySourceFeatures("places-boosted", { filter }).length > 0; keep the
existing non-boosted branch querying "places", and continue to fall back to
hidden when map, pulseMarker, or pulsePinId are missing.
🤖 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.

Outside diff comments:
In `@src/routes/map/`+page.svelte:
- Around line 228-242: updatePulseVisibility currently assumes boosted features
exist when isBoosted(place) && shouldClusterBoostedAtZoom(map.getZoom()),
causing an orphan pulse; update the function (updatePulseVisibility) to query
the correct source when the place is boosted and above threshold (use
"places-boosted" instead of "places") by constructing the same filter (["all",
["!", ["has","point_count"]], ["==", ["get","id"], pulsePinId]]) and setting
visible based on map.querySourceFeatures("places-boosted", { filter }).length >
0; keep the existing non-boosted branch querying "places", and continue to fall
back to hidden when map, pulseMarker, or pulsePinId are missing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 799b9c0d-8298-4fee-958d-3116bf68d6a7

📥 Commits

Reviewing files that changed from the base of the PR and between e704485 and d9adb91.

📒 Files selected for processing (3)
  • src/lib/map/boostedClustering.test.ts
  • src/lib/map/boostedClustering.ts
  • src/routes/map/+page.svelte

Copilot AI 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.

Pull request overview

This PR restores the legacy “boosted markers cluster when zoomed out” behavior in the MapLibre-based /map by dynamically routing boosted places into the clustered vs. non-clustered GeoJSON sources based on zoom, and re-syncing when crossing the zoom boundary.

Changes:

  • Added $lib/map/boostedClustering to decide whether boosted places should be clustered at a given zoom and to route places into places vs places-boosted.
  • Updated /map source syncing to use the new routing and to re-sync on moveend when crossing BOOSTED_CLUSTERING_MAX_ZOOM.
  • Added Vitest unit tests covering zoom-based routing, expired boosts, and deleted places.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/routes/map/+page.svelte Routes boosted places into clustered vs. standalone sources based on zoom; re-syncs routing on zoom boundary crossings.
src/lib/map/boostedClustering.ts Introduces zoom gate + routing helper for boosted clustering behavior.
src/lib/map/boostedClustering.test.ts Adds unit tests validating routing behavior across zoom thresholds and place states.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/map/boostedClustering.ts Outdated
MapLibre zoom is fractional but clustering transitions on integer zoom levels (the regular places source declusters at z17+ via clusterMaxZoom). Comparing the raw zoom (zoom <= 5) declustered boosted markers at display zoom 5.x, so they broke out of clusters earlier than the legacy 'zoom 1-5 clustered, 6+ standalone' intent while regular places were still clustered at the z5 level.

Floor the zoom so boosted markers stay clustered across the whole integer-5 band and only break out at z6. Adds fractional-zoom regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@escapedcat escapedcat merged commit 9bf268d into main Jun 11, 2026
12 checks passed
@escapedcat escapedcat deleted the fix/boosted-marker-low-zoom-clustering branch June 11, 2026 17:32
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.

2 participants