Skip to content

feat: add warp route ID search and pending message status filter - #263

Merged
nambrot merged 8 commits into
mainfrom
feat/pending-status-filter
Feb 4, 2026
Merged

feat: add warp route ID search and pending message status filter#263
nambrot merged 8 commits into
mainfrom
feat/pending-status-filter

Conversation

@nambrot-agent

@nambrot-agent nambrot-agent commented Jan 29, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds two new search/filter capabilities to the explorer:

  1. Warp Route ID Search - Search for messages by warp route ID (e.g., USDC/ethereum-base) directly in the search bar
  2. Pending Status Filter - Filter messages by delivery status (All/Delivered/Pending)

Changes

Warp Route ID Search

  • Detects warp route ID format (SYMBOL/route-name) in the search input
  • Looks up token addresses from the registry and queries by sender/recipient
  • Shows loading state while registry data loads
  • Shows "Warp route not found" if route ID doesn't exist

Status Filter

  • Added status selector (All/Delivered/Pending) to the filter bar
  • For pending filter, uses client-side filtering with larger batch size (500) since DB query for is_delivered=false is slow (no index on absence of record)
  • Fetches more messages and filters client-side for pending status

Technical Details

Why client-side filtering for pending?

The message_view computes is_delivered as dmsg.id IS NOT NULL from a LEFT JOIN with the delivered_message table. Filtering for is_delivered = false requires a full table scan since there's no index for "absence of record". Filtering for is_delivered = true is fast because it can use the delivered_message_msg_id_idx index.

Files Changed

  • src/store.ts - Added warp route ID to addresses map from registry
  • src/types.ts - Added MessageStatusFilter and WarpRouteIdToAddressesMap types
  • src/components/search/SearchFilterBar.tsx - Added StatusSelector component
  • src/features/messages/MessageSearch.tsx - Integrated warp route search and status filter
  • src/features/messages/queries/build.ts - Added status filter clause and warp route address filtering
  • src/features/messages/queries/useMessageQuery.ts - Added client-side pending filter with larger batch size

Summary by CodeRabbit

  • New Features

    • Status filter for message search (All / Delivered / Pending) with UI selector and URL persistence
    • Enhanced warp-route search: automatic ID detection, address-based queries, updated placeholder, and specific loading/“not found” feedback
  • Bug Fixes / UX

    • Search behavior tuned to avoid invalid queries and refine auto-refresh for pending/status-filtered results
  • Tests

    • Unit tests for warp-route ID format detection and string sanitization
  • Chores

    • Updated build/transpile configuration for tooling compatibility

- Add status filter (All/Delivered/Pending) to the search filter bar
- For pending filter, use client-side filtering since DB query for
  is_delivered=false is slow (no index on absence of record)
- Implement progressive loading with 'Load More' button for pending
  filter to allow fetching older messages
- Use larger batch size (500) for pending filter since most messages
  are delivered quickly
- Add warp route ID search support (e.g., 'USDC/mainnet-cctp')
- Build warp route ID to addresses map from registry data
@nambrot-agent
nambrot-agent requested a review from Xaroz as a code owner January 29, 2026 21:36
@vercel

vercel Bot commented Jan 29, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
hyperlane-explorer Ready Ready Preview, Comment Feb 3, 2026 9:58pm

Request Review

@coderabbitai

coderabbitai Bot commented Jan 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds message status filtering and warp-route ID detection; resolves warp-route addresses from store; extends query builder and query hook to accept status and warp-route address filters (including pending-specific batching and client-side filtering); introduces warp-route maps and hook in the store, new types, utility and tests, and expands transpile packages.

Changes

Cohort / File(s) Summary
Search UI
src/components/search/SearchFilterBar.tsx
Adds statusFilter prop and onChangeStatus, inserts StatusSelector, and adjusts layout to wrap filters.
Search Page / UX
src/features/messages/MessageSearch.tsx
Detects warp-route ID input, resolves addresses via store map, preserves/trims input in URL, adds STATUS query param, shows loading/unknown warp-route states, and wires statusFilter into UI and query flow.
Query Builder
src/features/messages/queries/build.ts
Extends buildMessageSearchQuery signature with statusFilter, warpRouteAddresses, isPendingFilter; adds status- and warp-route-specific WHERE clauses and variable wiring.
Query Execution / Hook
src/features/messages/queries/useMessageQuery.ts
Accepts statusFilter and warpRouteAddresses, adds pending-aware batching and client-side pending filtering, renames refetchrefresh, and centralizes auto-refresh via interval.
Store & Warp-Route Maps
src/store.ts
Adds warpRouteIdToAddressesMap and setter to AppState, replaces map builder with buildWarpRouteMaps, persists/rehydrates new map, exposes useWarpRouteIdToAddressesMap, and updates multi-provider return shape.
Types
src/types.ts
Adds WarpRouteIdToAddressesMap and MessageStatusFilter (`'all'
Utils & Tests
src/utils/string.ts, src/utils/string.test.ts
Adds isWarpRouteIdFormat utility and unit tests; expands sanitizeString tests to cover warp-route ID detection and edge cases.
Config
next.config.js
Expands transpilePackages list to include multiple @hyperlane-xyz/* packages and lodash-es.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Search UI
    participant Store as App Store
    participant QB as Query Builder
    participant API as GraphQL API
    participant Results

    User->>UI: Enter search text or warp-route ID and select status
    UI->>Store: Read warpRouteIdToAddressesMap
    Store-->>UI: Return mapping
    alt input is warp-route ID
        UI->>UI: Determine looksLikeWarpRoute / isUnknownWarpRoute
        UI->>QB: Build query with warpRouteAddresses + statusFilter
    else regular text
        UI->>UI: Sanitize input
        UI->>QB: Build query with sanitized input + statusFilter
    end
    QB->>API: Execute GraphQL query (includes warp addresses/status vars)
    API-->>QB: Return messages batch
    QB->>QB: Apply client-side pending filter if statusFilter == 'pending'
    QB-->>Results: Render messages
    User->>UI: Request more
    UI->>QB: Fetch next batch
    QB->>API: Fetch next batch
    API-->>Results: Append messages
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

A route was found where mud once lay,
Filters set and queries play.
Maps whisper addresses neat,
Pending hunts a smaller beat.
Fetches hum — results display.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 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 two main features added: warp route ID search and pending message status filtering, matching the changeset's primary objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/pending-status-filter

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.

@nambrot

nambrot commented Jan 29, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 29, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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

Caution

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

⚠️ Outside diff range comments (1)
src/features/messages/queries/build.ts (1)

84-184: Filter undefined values from warp address conversion to keep queries valid.

searchValueToPostgresBytea returns string | undefined, so warpAddressesBytea can contain undefined values. These undefined values would violate the [bytea!] type declaration (non-null bytea array) and could break the query. Additionally, hasFilters checks the raw warpRouteAddresses.length while the variable uses the converted array—if all addresses fail conversion, the mismatch means the query might declare $warpAddresses inconsistently.

Filter out undefined values when converting, use the filtered array's length for hasFilters, and pass the filtered array to buildWarpRouteWhereClause:

Fix
-const warpAddressesBytea = warpRouteAddresses.map((addr) => searchValueToPostgresBytea(addr));
+const warpAddressesBytea = warpRouteAddresses
+  .map((addr) => searchValueToPostgresBytea(addr))
+  .filter((addr): addr is string => !!addr);

...
-    warpRouteAddresses.length > 0
+    warpAddressesBytea.length > 0

...
-const warpRouteWhereClause = buildWarpRouteWhereClause(warpRouteAddresses);
+const warpRouteWhereClause = buildWarpRouteWhereClause(warpAddressesBytea);

...
-function buildWarpRouteWhereClause(warpRouteAddresses: string[]): string {
-  if (warpRouteAddresses.length === 0) return '';
+function buildWarpRouteWhereClause(warpAddressesBytea: string[]): string {
+  if (warpAddressesBytea.length === 0) return '';
   return '{_or: [{sender: {_in: $warpAddresses}}, {recipient: {_in: $warpAddresses}}]},';
 }
🤖 Fix all issues with AI agents
In `@src/features/messages/queries/useMessageQuery.ts`:
- Around line 132-154: The useEffect currently bails out early when data is
falsy so setIsLoadingMore(false) never runs on failed/empty fetches; update the
effect (watching the query's isFetching) so it doesn't return before clearing
the loading flag and explicitly call setIsLoadingMore(false) whenever isFetching
transitions to false (regardless of data), e.g. include isFetching in the
dependency list and handle the isFetching === false case before or alongside the
existing data handling inside the useEffect that references
parseMessageStubResult, currentOffset, setAccumulatedMessages, and
setHasMorePages.

Comment thread src/features/messages/queries/useMessageQuery.ts Outdated
Comment thread src/features/messages/queries/useMessageQuery.ts Outdated
Comment thread src/features/messages/queries/useMessageQuery.ts
…ger request

- Remove all pagination/load more logic for pending filter
- Use larger batch size (500) only when pending filter is active
- Keep normal query limits (50/100) for other searches
- Apply client-side filtering for pending messages
- Fix warp addresses bytea conversion to filter out invalid addresses
- Address PR review feedback to simplify the implementation
@nambrot-agent nambrot-agent changed the title feat: add pending message status filter with progressive loading feat: add warp route ID search and pending message status filter Jan 29, 2026
@nambrot
nambrot enabled auto-merge (squash) January 29, 2026 22:21
@paulbalaji

Copy link
Copy Markdown
Collaborator

PR Review Summary

Overall

Good feature additions - warp route ID search and pending status filter are useful. CI passes. A few issues to address:


🔴 Bug: Pending Filter Silently Excludes Testnet Messages (P1)

Location: useMessageQuery.ts:63-66 + build.ts:96-104

When user selects "Pending" without other filters, testnet messages are excluded. But "Delivered" shows both mainnet AND testnet. This inconsistency happens because:

// useMessageQuery.ts
const dbStatusFilter = isPendingFilter ? 'all' : statusFilter;  // pending → 'all'
buildMessageSearchQuery(..., dbStatusFilter, ...)  // passes 'all' to build.ts
// build.ts  
const hasFilters = !!(
  // ...
  statusFilter !== 'all' ||  // 'all' !== 'all' = false!
);

When hasFilters is false, buildDomainIdWhereClause applies mainnet-only restriction (line 213).

Suggested fix: Pass an isPendingFilter flag to buildMessageSearchQuery and include it in the hasFilters check:

const hasFilters = !!(
  // ...existing conditions...
  statusFilter !== 'all' ||
  isPendingFilter ||  // ← Add this
  warpAddressesBytea.length > 0
);

🟡 Minor: Warp Route ID Detection (P3)

isWarpRouteIdFormat uses a loose check (slashCount === 1 && length > 2). The registry uses stricter pattern: ^([a-zA-Z0-9.*]+)/([a-z0-9-]+)$. Consider aligning for consistency.


✅ What's Good

  1. CodeRabbit's build.ts issue addressed - Properly filters undefined values from warp address conversion
  2. Simplified implementation - Final commit removed complex pagination in favor of larger batch + client-side filter
  3. Good UX - Loading state while registry loads, "not found" message for unknown routes
  4. URL state sync - Status filter preserved in URL for shareability
  5. Clean StatusSelector - Follows existing component patterns

Test Suggestions

  1. Select "Pending" without other filters → verify testnet pending messages appear
  2. Search USDC/mainnet-cctp → verify warp route messages appear
  3. Search invalid route like INVALID/foo → verify "not found" message
  4. Combine pending + origin chain filter → verify works correctly

Comment thread src/features/messages/MessageSearch.tsx Outdated
Comment thread src/types.ts
- Move isWarpRouteIdFormat function from MessageSearch.tsx to utils/string.ts
- Add unit tests for isWarpRouteIdFormat and sanitizeString in string.test.ts
- Add all @hyperlane-xyz/* packages to transpilePackages in next.config.js
  to fix Jest ESM module resolution with pnpm

@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: 2

🤖 Fix all issues with AI agents
In `@src/features/messages/queries/build.ts`:
- Around line 101-109: The current hasFilters boolean misses the "pending"
client-side filter so testnet pending messages get filtered out; update the
function signature (the query builder function in
src/features/messages/queries/build.ts) to accept an extra boolean parameter
isPendingFilter, include isPendingFilter in the hasFilters expression (i.e.
treat pending as a filter alongside originDomainIdFilter, destDomainIdFilter,
startTimeFilter, endTimeFilter, searchInput, statusFilter check, and
warpAddressesBytea), and update callers (e.g., useMessageQuery.ts where
dbStatusFilter is 'all' for pending) to pass the appropriate isPendingFilter
flag so buildDomainIdWhereClause no longer forces mainnet-only when pending is
the only filter.

In `@src/features/messages/queries/useMessageQuery.ts`:
- Around line 72-83: The call to buildMessageSearchQuery in useMessageQuery.ts
omits the isPendingFilter flag, so buildMessageSearchQuery can't include pending
status in its hasFilters logic and testnet messages get excluded; update the
invocation of buildMessageSearchQuery (the const { query, variables } =
buildMessageSearchQuery(...) line) to pass the isPendingFilter boolean as an
additional argument (after warpAddresses or in the correct position expected by
buildMessageSearchQuery) so the function can account for pending-filter when
computing hasFilters and building the query.
🧹 Nitpick comments (3)
src/utils/string.ts (1)

24-29: Consider tightening the format validation, if ye don't mind.

The current check is fairly relaxed - it'll accept things like 123/456 or inputs with special characters that aren't valid warp route IDs per the registry pattern. Since the downstream lookup in warpRouteIdToAddressesMap handles unknown routes gracefully, this isn't a swamp-level crisis. But if ye want to catch obviously invalid inputs earlier (before the map lookup), consider aligning with the registry pattern:

♻️ Optional: stricter regex validation
 export function isWarpRouteIdFormat(input: string): boolean {
   const trimmed = input.trim();
   if (!trimmed || trimmed.length <= 2) return false;
   if (trimmed.startsWith('0x')) return false;
-  const slashCount = (trimmed.match(/\//g) || []).length;
-  return slashCount === 1;
+  // Match registry pattern: SYMBOL/route-name
+  return /^[a-zA-Z0-9.*]+\/[a-z0-9-]+$/.test(trimmed);
 }
src/utils/string.test.ts (1)

19-39: Tests look solid, but there's one wee edge case lurkin' in the swamp.

The tests cover the main scenarios well. One thing worth adding: input that starts with a slash like /abc would currently pass the validation (length > 2, one slash, no 0x prefix). If that's not a valid warp route format, consider adding a test case for it.

💡 Optional: additional edge case test
   // Too short
   expect(isWarpRouteIdFormat('')).toBe(false);
   expect(isWarpRouteIdFormat('/')).toBe(false);
   expect(isWarpRouteIdFormat('a/')).toBe(false);
+
+  // Starts with slash (invalid format)
+  expect(isWarpRouteIdFormat('/abc')).toBe(false);
+  expect(isWarpRouteIdFormat('/ethereum-base')).toBe(false);
 
   // Just whitespace
   expect(isWarpRouteIdFormat('   ')).toBe(false);
 });
src/features/messages/MessageSearch.tsx (1)

257-276: The loading and error states look fine, but consider a wee accessibility touch.

The states render correctly and give good feedback. For screen reader users, ye might want to add an aria-live="polite" attribute so the status changes are announced automatically.

💡 Optional: accessibility enhancement
-        {looksLikeWarpRoute && !isWarpRouteMapLoaded && (
-          <div className="absolute left-0 right-0 top-10">
+        {looksLikeWarpRoute && !isWarpRouteMapLoaded && (
+          <div className="absolute left-0 right-0 top-10" aria-live="polite">

Comment thread src/features/messages/queries/build.ts
Comment thread src/features/messages/queries/useMessageQuery.ts
When pending filter is active, we pass dbStatusFilter='all' to avoid
slow DB queries. However, this caused hasFilters to be false when no
other filters were set, which restricted results to mainnet-only.

Fix by passing isPendingFilter as a separate flag and including it
in the hasFilters calculation.
Comment thread src/features/messages/MessageSearch.tsx
Comment thread src/features/messages/queries/useMessageQuery.ts
Comment thread src/utils/string.ts
nambrot and others added 2 commits February 3, 2026 16:52
Pending filter uses client-side filtering, so this branch never executes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@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

🤖 Fix all issues with AI agents
In `@src/features/messages/queries/build.ts`:
- Around line 84-87: The current map+filter silently drops invalid warp-route
addresses when building warpAddressesBytea; update the logic around
warpRouteAddresses and searchValueToPostgresBytea so invalid conversions are not
swallowed—detect any addr where searchValueToPostgresBytea(addr) returns falsy
and either throw an Error (including the offending addr and context) or log a
clear processLogger.error/console.error and then throw; ensure the failure
occurs before proceeding with the query so broken registry data cannot widen
results or trigger a default path.

Comment thread src/features/messages/queries/build.ts
@nambrot
nambrot merged commit d4c0480 into main Feb 4, 2026
15 checks passed
@nambrot
nambrot deleted the feat/pending-status-filter branch February 4, 2026 17:08
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.

4 participants