feat(route-explorer): Sprint 6 — free-tier blur + analytics + docs#3000
Conversation
Free-tier treatment:
- Non-PRO users see a public route highlight on the map using only
TRADE_ROUTES + COUNTRY_PORT_CLUSTERS (no PRO RPC calls)
- Left rail blurred with filter:blur(6px) + pointer-events:none
- Tab content replaced with upgrade CTA (startCheckout wired)
- Zero requests to get-route-explorer-lane or get-route-impact for
free users (network-verifiable)
Analytics (8 events, type-safe via UmamiEvent):
- route-explorer:opened (tier, source)
- route-explorer:query (from, to, hs2, cargo, tier)
- route-explorer:tab-switch (tab, tier)
- route-explorer:alternative-selected (corridorId, tier)
- route-explorer:impact-viewed (toIso2, hs2, tier)
- route-explorer:share-copied (tier)
- route-explorer:free-cta-click (from, to, hs2)
- route-explorer:closed (durationSec, queryCount, tier)
- trackGateHit('route-explorer') on first free-tier open per session
Documentation:
- docs/maritime-intelligence.mdx: Route Explorer section with feature list
- CHANGELOG.md + docs/changelog.mdx dual-updated with feature entry
Plan status updated to completed.
E2E Playwright specs deferred: need running dev server + authenticated
PRO session. Manual verification recommended via the test plan in PR.
Plan: docs/plans/2026-04-11-001-feat-worldwide-route-explorer-plan.md
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis sprint completes the Route Explorer feature by adding free-tier gating (blurred left rail + upgrade CTA with public route highlight), 8 type-safe analytics events wired through the existing Umami facade, and documentation updates across Confidence Score: 5/5Safe to merge — no P0/P1 issues; all findings are minor style and maintenance suggestions. All four findings are P2: a redundant analytics call, a hardcoded event property, an accessibility gap on the blurred rail, and CSS duplicate declarations. None affect runtime correctness, data integrity, or the primary user path. src/styles/route-explorer.css has duplicate rule declarations that should be cleaned up; src/components/RouteExplorer/RouteExplorer.ts has minor analytics and accessibility polish items. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User opens Route Explorer] --> B{Query complete?\nfrom + to + hs2}
B -- No --> C[Focus empty field]
B -- Yes --> D[scheduleFetch + track query event]
D --> E{hasPremiumAccess?}
E -- PRO --> F[fetchRouteExplorerLane RPC]
F --> G{Response OK?}
G -- Success --> H[applyData + applyMapState]
H --> I[fetchResilience + fetchImpact async]
I --> J{Impact data comtradeSource = bilateral-hs4?}
J -- Yes --> K[track impact-viewed event]
G -- Error --> L[showError + resetLaneState]
E -- Free --> M[generationId++ / resetLaneState gate]
M --> N[renderFreeGate: blur left rail + show CTA]
N --> O[applyPublicRouteHighlight via COUNTRY_PORT_CLUSTERS]
O --> P{Shared routes found?}
P -- Yes --> Q[highlightRoute shared route]
P -- No --> R[fallback: from-country first route]
N --> S{User clicks Upgrade CTA}
S --> T[track free-cta-click + startCheckout pro_monthly]
Reviews (1): Last reviewed commit: "feat(route-explorer): Sprint 6 — free-ti..." | Re-trigger Greptile |
| btn?.addEventListener('click', () => { | ||
| trackGateHit('route-explorer'); |
There was a problem hiding this comment.
Redundant
trackGateHit on upgrade CTA click
trackGateHit('route-explorer') is already fired (with the gateHitTracked guard) when the modal opens for a free user. Firing it again on every CTA click produces duplicate gate-hit events in Umami alongside the already-dedicated route-explorer:free-cta-click event, which creates noise in the gate-hit funnel. Consider removing this call from the click handler and relying solely on route-explorer:free-cta-click for CTA interaction tracking.
| btn?.addEventListener('click', () => { | |
| trackGateHit('route-explorer'); | |
| btn?.addEventListener('click', () => { | |
| this.trackEvent('route-explorer:free-cta-click', { |
| trackGateHit('route-explorer'); | ||
| this.gateHitTracked = true; | ||
| } | ||
| this.trackEvent('route-explorer:opened', { source: 'cmdk' }); |
There was a problem hiding this comment.
source hardcoded to 'cmdk' for all open paths
The PR description notes that the modal can be auto-opened via ?explorer=from:CN,to:DE,hs:85 URL state. Since open() takes no source parameter, every route-explorer:opened event will report source: 'cmdk' even for URL-initiated opens, making it impossible to distinguish entry points in Umami. Consider adding an optional source parameter to open() and passing it through to the event.
| private renderFreeGate(): void { | ||
| this.leftRail?.element.classList.add('re-leftrail--blurred'); | ||
| if (this.contentEl) { |
There was a problem hiding this comment.
Blurred left rail remains accessible to screen readers
filter: blur(6px) and pointer-events: none are purely visual — keyboard focus and screen readers can still tab into and read the blurred left-rail content. Users on assistive tech can extract the gated data without seeing it blurred. Setting aria-hidden="true" on the rail when the gate is applied would close this gap.
| private renderFreeGate(): void { | |
| this.leftRail?.element.classList.add('re-leftrail--blurred'); | |
| if (this.contentEl) { | |
| this.leftRail?.element.classList.add('re-leftrail--blurred'); | |
| this.leftRail?.element.setAttribute('aria-hidden', 'true'); |
| .re-content__gate { | ||
| position: relative; | ||
| z-index: 2; | ||
| } | ||
|
|
||
| .re-content__gate h3 { | ||
| margin: 0 0 12px; | ||
| color: var(--text-primary, #c9d1d9); | ||
| font-size: 18px; | ||
| } | ||
|
|
||
| .re-content__gate ul { | ||
| text-align: left; | ||
| margin: 0 0 16px; | ||
| padding-left: 20px; | ||
| font-size: 14px; | ||
| color: var(--text-dim, #8b949e); | ||
| } | ||
|
|
||
| .re-content__upgrade { | ||
| padding: 10px 24px; | ||
| background: var(--accent, #58a6ff); | ||
| color: #fff; | ||
| border: none; | ||
| border-radius: 8px; | ||
| font-size: 15px; | ||
| font-weight: 600; | ||
| cursor: pointer; | ||
| transition: background 0.15s; | ||
| } | ||
| .re-content__upgrade:hover { background: #4090ef; } |
There was a problem hiding this comment.
Duplicate CSS rule declarations
The sprint 6 free-tier section re-declares .re-content__gate h3, .re-content__gate ul, and .re-content__upgrade — all three were already defined earlier in the file (around lines 268–280). The later rules win for conflicting properties, but the earlier definitions still exist with contradicting values (padding: 8px 20px vs 10px 24px, border-radius: 6px vs 8px, font-size: 14px vs 15px). The original declarations at lines 268–280 should be removed or merged so there is a single source of truth for each selector.
1. No-shared-route pairs no longer highlight a fallback route. When fromIso2 and toIso2 clusters share no routes, applyPublicRouteHighlight returns early without highlighting, matching the server's noModeledLane behavior that clears primaryRouteId and geometry. 2. Shared routes are now ranked by cargo-category compatibility before picking the primary route, matching the server's rankSharedRoutesByCargo logic. Uses the same CARGO_TO_ROUTE_CATEGORY mapping (tanker→energy, container→container, bulk→bulk, roro→container) and TRADE_ROUTES category lookup so free and PRO users see the same primary route for the same query.
P2: removed redundant trackGateHit from CTA click handler (already
fired once on modal open via gateHitTracked guard; route-explorer:free-
cta-click is the dedicated CTA event).
P2: open() now accepts a source parameter ('cmdk'|'url'|'icon') so
route-explorer:opened events distinguish entry points in Umami.
P2: blurred left rail now sets aria-hidden="true" so screen readers
cannot read gated content. Removed on PRO data load alongside the
blur class.
P2: removed duplicate CSS declarations for .re-content__gate h3,
.re-content__gate ul, and .re-content__upgrade from the earlier
section; Sprint 6 section is now the single source of truth.
Summary
Final sprint of the Route Explorer feature. Adds free-tier blur treatment, 8 analytics events, and documentation updates. The entire 6-sprint Route Explorer feature is now complete.
Free-tier treatment
TRADE_ROUTES+COUNTRY_PORT_CLUSTERS(zero PRO RPC calls)filter: blur(6px)+pointer-events: nonestartCheckout('pro_monthly')wired +trackGateHit)get-route-explorer-laneorget-route-impactfor free usersCOUNTRY_PORT_CLUSTERSshared-route matching as the PRO path, but from client-side data onlyAnalytics (8 type-safe events)
route-explorer:openedroute-explorer:queryroute-explorer:tab-switchroute-explorer:alternative-selectedroute-explorer:impact-viewedroute-explorer:share-copiedroute-explorer:free-cta-clickroute-explorer:closedAll events added to
EVENTSconst inanalytics.tsfor type-safeUmamiEventunion.Documentation
docs/maritime-intelligence.mdx: new Route Explorer section with feature listCHANGELOG.md+docs/changelog.mdx: dual-updated with feature entry referencing all sprint PRsPlan status
docs/plans/2026-04-11-001-feat-worldwide-route-explorer-plan.mdstatus updated fromdrafttocompleted.Deferred
e2e/route-explorer.spec.ts): needs running dev server + authenticated PRO session. Recommended for manual verification using the test plan below.Test plan
umami→ verify events fire on open/query/tab-switch/close?explorer=from:CN,to:DE,hs:85→ modal opens with correct statePost-Deploy Monitoring & Validation
route-explorer:openedevent volume in Umami dashboardget-route-explorer-lanerequestsroute-explorer:free-cta-clickevents without corresponding checkout session starts