Skip to content

feat(route-explorer): Sprint 6 — free-tier blur + analytics + docs#3000

Merged
koala73 merged 3 commits into
mainfrom
feat/route-explorer-sprint6
Apr 12, 2026
Merged

feat(route-explorer): Sprint 6 — free-tier blur + analytics + docs#3000
koala73 merged 3 commits into
mainfrom
feat/route-explorer-sprint6

Conversation

@koala73
Copy link
Copy Markdown
Owner

@koala73 koala73 commented Apr 12, 2026

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

  • Non-PRO users see a public route highlight on the map using only TRADE_ROUTES + COUNTRY_PORT_CLUSTERS (zero PRO RPC calls)
  • Left rail blurred with filter: blur(6px) + pointer-events: none
  • Tab content replaced with upgrade CTA (startCheckout('pro_monthly') wired + trackGateHit)
  • Network-verifiable: zero requests to get-route-explorer-lane or get-route-impact for free users
  • Public route lookup uses the same COUNTRY_PORT_CLUSTERS shared-route matching as the PRO path, but from client-side data only

Analytics (8 type-safe events)

Event When Properties
route-explorer:opened Modal open tier, source
route-explorer:query Chip commit from, to, hs2, cargo, tier
route-explorer:tab-switch Tab change tab, tier
route-explorer:alternative-selected Bypass selected corridorId, tier
route-explorer:impact-viewed Impact data rendered toIso2, hs2, tier
route-explorer:share-copied Cmd+, copy tier
route-explorer:free-cta-click Upgrade CTA from, to, hs2
route-explorer:closed Modal close durationSec, queryCount, tier

All events added to EVENTS const in analytics.ts for type-safe UmamiEvent union.

Documentation

  • docs/maritime-intelligence.mdx: new Route Explorer section with feature list
  • CHANGELOG.md + docs/changelog.mdx: dual-updated with feature entry referencing all sprint PRs

Plan status

docs/plans/2026-04-11-001-feat-worldwide-route-explorer-plan.md status updated from draft to completed.

Deferred

  • E2E Playwright spec (e2e/route-explorer.spec.ts): needs running dev server + authenticated PRO session. Recommended for manual verification using the test plan below.

Test plan

  • PRO: CMD+K → "route" → CN→DE HS 85 → all 4 tabs render with data → map highlights route
  • Free: open explorer without PRO session → left rail blurred → upgrade CTA visible → chip changes update map with public route → no PRO RPC calls in network tab
  • Free: click "Upgrade to PRO" → checkout flow opens (or fallback to worldmonitor.app/pro)
  • Analytics: open browser console → filter for umami → verify events fire on open/query/tab-switch/close
  • URL state: load ?explorer=from:CN,to:DE,hs:85 → modal opens with correct state
  • Keyboard: F/T/P/S/1-4/?/Esc all work as documented

Post-Deploy Monitoring & Validation

  • What to monitor: route-explorer:opened event volume in Umami dashboard
  • Expected healthy: events fire for both PRO and free tiers; free-tier shows zero get-route-explorer-lane requests
  • Failure signal: route-explorer:free-cta-click events without corresponding checkout session starts
  • Validation window: 48h post-deploy

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
@mintlify
Copy link
Copy Markdown

mintlify Bot commented Apr 12, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
WorldMonitor 🟢 Ready View Preview Apr 12, 2026, 7:14 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Apr 12, 2026 7:31am

Request Review

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 12, 2026

Greptile Summary

This 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 maritime-intelligence.mdx, CHANGELOG.md, and docs/changelog.mdx. The implementation is clean and consistent with the codebase's patterns — generation-ID guards, debounce, and the trackEvent tier-injection wrapper all work correctly. All remaining findings are P2 style/maintenance items.

Confidence Score: 5/5

Safe 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

Filename Overview
src/components/RouteExplorer/RouteExplorer.ts Core component adding free-gate path, analytics events, and public route highlight; contains a redundant trackGateHit call in the CTA click handler and a hardcoded source: 'cmdk' in the opened event.
src/services/analytics.ts Adds 8 type-safe Route Explorer events to the EVENTS const; implementation is clean and follows existing patterns.
src/styles/route-explorer.css Adds free-tier blur class and gate CTA styles, but the sprint 6 additions duplicate .re-content__gate h3, .re-content__gate ul, and .re-content__upgrade rules already defined earlier in the file.
docs/maritime-intelligence.mdx Adds Route Explorer section to maritime docs with accurate feature descriptions; clean and well-structured.
CHANGELOG.md Route Explorer feature entry added with PR references; format matches existing changelog conventions.
docs/changelog.mdx MDX changelog updated in sync with CHANGELOG.md; no issues.

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]
Loading

Reviews (1): Last reviewed commit: "feat(route-explorer): Sprint 6 — free-ti..." | Re-trigger Greptile

Comment on lines 335 to 336
btn?.addEventListener('click', () => {
trackGateHit('route-explorer');
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.

P2 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.

Suggested change
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' });
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.

P2 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.

Comment on lines 325 to 327
private renderFreeGate(): void {
this.leftRail?.element.classList.add('re-leftrail--blurred');
if (this.contentEl) {
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.

P2 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.

Suggested change
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');

Comment on lines +621 to +651
.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; }
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.

P2 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.
@koala73 koala73 merged commit c081556 into main Apr 12, 2026
10 of 11 checks passed
@koala73 koala73 deleted the feat/route-explorer-sprint6 branch April 12, 2026 07:31
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