fix(merchant): make map deep link select the merchant #966#1047
Conversation
…966 The hero's "View on main map" link encoded the merchant in the URL hash (/map#18/lat/lng&merchant=123), but since #1019 ("switch merchant URL from hash to query params") the map reads the merchant only from the query string via parseMerchantHash(). With an empty query string the parser bailed out, so the map opened at the right viewport but never selected the marker or opened the drawer. - Extract buildMerchantMapHref() as the single source of truth for the /map?merchant=…#zoom/lat/lng deep link, used by MerchantHero - Refresh the stale mapHash.ts header comment (merchant/view live in the query string, not the hash) - Add a round-trip regression test: the link the helper builds must resolve back through parseMerchantHash + parseHashCoords (proven to fail on the old hash format) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
✅ Deploy Preview for btcmap ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
💤 Files with no reviewable changes (1)
📝 WalkthroughWalkthroughStandardizes merchant deep links (merchant in query, viewport in hash), adds buildMerchantMapHref and tests, updates MerchantHero to use it, introduces CSS pulse overlay, and implements map-page reveal, pulse Marker, lifted pin behavior, and cleanup. ChangesMerchant Deep-Link URL Format Standardization & Selected-Pin Pulse
sequenceDiagram
participant Client
participant Router
participant MapPage
participant parseMerchantHash
participant maplibre_Marker
Client->>Router: click /map?merchant=...#zoom/lat/lng
Router->>MapPage: navigate to /map with URL
MapPage->>parseMerchantHash: parse merchant from query + hash coords
MapPage->>MapPage: pan/zoom to REVEAL_ZOOM (if needed)
MapPage->>maplibre_Marker: create/update pulse Marker at coords
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 `@src/lib/merchantDrawerHash.ts`:
- Around line 78-85: The buildMerchantMapHref function currently injects
merchantId raw into the query string which can break URLs if it contains
reserved characters; update buildMerchantMapHref to URL-encode merchantId (e.g.,
call encodeURIComponent(String(merchantId))) when interpolating the query param
so the returned string is safe and cannot corrupt the hash/fragment
(`#zoom/lat/lng`).
🪄 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: 025e5544-5f97-4056-85a9-e2e6f70fc466
📒 Files selected for processing (4)
src/lib/map/mapHash.tssrc/lib/merchantDrawerHash.test.tssrc/lib/merchantDrawerHash.tssrc/routes/merchant/[id]/components/MerchantHero.svelte
|
Good catch! |
There was a problem hiding this comment.
Pull request overview
This PR fixes the merchant page’s “View on main map” deep link so it selects/open the correct merchant drawer after the #1019 switch from hash-based merchant IDs to query-string merchant IDs.
Changes:
- Replaces the merchant hero map link with a new
buildMerchantMapHref()helper that encodesmerchantin the query string and viewport in the hash. - Updates
mapHash.tsheader documentation to reflect that only viewport data lives in the hash. - Adds a regression/round-trip test ensuring the generated link is readable by both
parseMerchantHash()andparseHashCoords().
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/routes/merchant/[id]/components/MerchantHero.svelte | Uses the new canonical helper to build the “View on main map” deep link. |
| src/lib/merchantDrawerHash.ts | Adds buildMerchantMapHref() as the single source of truth for merchant map deep links. |
| src/lib/merchantDrawerHash.test.ts | Adds round-trip and regression tests for the new deep-link format. |
| src/lib/map/mapHash.ts | Updates header comment to document query-vs-hash responsibilities post-#1019. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Implements design C ("centered pulsing locator") from the Claude Design
handoff. Selecting a pin scales it up and drops a DOM overlay marker on its
geo point: a pulsing ring + a center dot in the pin's own hue (teal / bitcoin
orange). The pulse is pure CSS keyframes (composited, honors
prefers-reduced-motion); the GL pin layer is untouched apart from the
icon-size lift.
- maplibre is imported dynamically, so the namespace is stashed in onMount to
build the Marker reactively
- the pulse repositions on selection change and on $places (so a deep-linked
merchant gets its pulse once places sync in); removed on deselect + onDestroy
- replaces the orphaned Leaflet-era .selected-marker glow CSS
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ster state #1003 The locator pulse is pinned to the merchant's coordinate regardless of clustering, so it floated orphaned whenever no individual pin was there to sit on. Two fixes: Reveal on selection — zoom past CLUSTERING_DISABLED_ZOOM (new REVEAL_ZOOM 17.5) on every selection path that could otherwise land clustered: - panToNearbyMerchant (nearby list) — was pan-only - panToPlace (deep-link pan) — was landing on the still-clustered DEFAULT_MAP_ZOOM (15) - the deep-link reveal now also fires when the URL's hash zoom is below the threshold (e.g. ?merchant=X#14/…); a hash zoom at/above 17 is honoured Search-result selection already zoomed past 17. Hide on re-cluster — when the user manually zooms out and the merchant is rolled back into a cluster, hide the pulse (no pin to highlight) and show it again once the pin renders individually. Checked against the live source cluster state on moveend/idle, since whether a point clusters depends on its neighbours, not the zoom level alone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1003 Code-review follow-ups: - syncSelectionPulse no longer early-returns for an unchanged selection, so boosting the selected merchant from the drawer (updateSinglePlace → $places) recolours the pulse teal → orange to match the now-orange pin. The colour/position refresh is idempotent on an already-added marker; the cluster-visibility query stays gated behind a real selection change. - pulse colours now come from PIN_FILL_REGULAR / PIN_FILL_BOOSTED (the GL pin sprite's source of truth) instead of duplicated literals, so they can't desync; the CSS fallback is documented as mirroring PIN_FILL_REGULAR. - dropped the redundant updatePulseVisibility() call on moveend; the idle handler is the authoritative reconcile point (avoids a premature/duplicate querySourceFeatures per interaction). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/app.css (1)
180-191: ⚡ Quick winConsider using
transform: scale()instead of animatingwidth/heightfor better performance.Animating
widthandheighttriggers layout recalculation on every frame. Since the comment mentions the pulse is "pure CSS (composited)", usingtransform: scale()would actually achieve compositor-only animation.♻️ Proposed refactor using transform: scale()
+ .bm-pulse-ring { + width: 70px; + height: 70px; + border: 3px solid var(--bm-pulse-color, `#0e95af`); + opacity: 0; + animation: bm-pulse 1.8s ease-out infinite; + } + `@keyframes` bm-pulse { 0% { - width: 18px; - height: 18px; + transform: translate(-50%, -50%) scale(0.257); opacity: 0.55; } 100% { - width: 70px; - height: 70px; + transform: translate(-50%, -50%) scale(1); opacity: 0; } }Note: The
translate(-50%, -50%)must be included in the keyframes sincetransformreplaces the base transform entirely.🤖 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.css` around lines 180 - 191, The keyframes named bm-pulse currently animates width/height which causes layout thrashing; update the `@keyframes` bm-pulse to animate transform: scale(...) (keeping opacity) instead of width/height and ensure you include the element's translate(-50%, -50%) in the keyframes because transform replaces the base transform; adjust the element's base width/height (the original 18px start size) in the static CSS so the visual sizes match the previous animation and keep the 0%/100% opacity values unchanged.
🤖 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.css`:
- Around line 180-191: The keyframes named bm-pulse currently animates
width/height which causes layout thrashing; update the `@keyframes` bm-pulse to
animate transform: scale(...) (keeping opacity) instead of width/height and
ensure you include the element's translate(-50%, -50%) in the keyframes because
transform replaces the base transform; adjust the element's base width/height
(the original 18px start size) in the static CSS so the visual sizes match the
previous animation and keep the 0%/100% opacity values unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9c03bb95-6736-49a6-90ff-8a66555689f3
📒 Files selected for processing (2)
src/app.csssrc/routes/map/+page.svelte
PR #1047 review (Copilot): the id was interpolated raw into the query string, so a non-numeric/crafted id could split into extra query params (e.g. merchant=123&view=boost) or break the URL. Encode it with encodeURIComponent. Place ids are numeric today (so it encodes to itself), making this defensive; the round-trip test is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aligned #1003 The icon-size lift scaled only the pin layers (unclustered-point/boosted-point), but the comment-count, saved and label layers are separate symbols pinned to fixed pixel offsets computed for icon-size 1, so a scaled-up pin left its badge detached. The pulsing locator is the primary selection cue, so drop the lift rather than re-scaling eight badge/label layers and their offsets. Removes setSelectedPinLift + its reactive and the now-unused SELECTED_PIN_SCALE / PIN_LAYER_IDS / liftedPinId / ExpressionSpecification import. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@dadofsambonzuki please give it try, re-added the highlight |

Started as a fix for the merchant page's "View on main map" deep link, then grew to cover
the selected-pin highlight and clustering.
from the query string (since fix(map): serve merchant OG image for ?merchant= URL param #1019), so nothing got selected. Extracted
buildMerchantMapHref()as the canonical/map?merchant=…#zoom/lat/lnglink (used byMerchantHero), refreshed the stalemapHash.tscomment, and url-encoded the id.locator" — a CSS-animated ring + dot in the pin's hue; honors
prefers-reduced-motion.Removed the orphaned Leaflet-era glow CSS.
its pin, and the pulse hides when the pin is rolled into a cluster (returning when it
declusters).
Tests: deep-link round-trip + a regression test for the legacy hash format.
Summary by CodeRabbit
the URL hash; merchant card “View on main map” uses the canonical link.
an individual pin when deep-linking/selecting.
Summary by CodeRabbit
New Features
Tests