Skip to content

feat(messages): add tip-stage raw fallback for near-head explorer visibility#281

Draft
paulbalaji wants to merge 6 commits intomainfrom
pbio/tip-stage-raw-fallback
Draft

feat(messages): add tip-stage raw fallback for near-head explorer visibility#281
paulbalaji wants to merge 6 commits intomainfrom
pbio/tip-stage-raw-fallback

Conversation

@paulbalaji
Copy link
Collaborator

@paulbalaji paulbalaji commented Feb 24, 2026

Summary

  • add raw tip-stage fallback reads from raw_message_dispatch for near-head visibility
  • keep finalized message_view as authoritative source when present
  • merge finalized + raw by msg_id and prefer finalized rows
  • add provisional marker (isProvisional) + tip badge in table
  • add OG query fallback to raw table for faster social previews
  • for provisional rows: still run delivery check, but defer deep debug until finalized to reduce false failing states

Query Scope

  • raw fallback enabled for detail (msg_id) and hash-like search inputs
  • raw fallback intentionally not enabled for broad latest/address scans to limit heavy table scans and stale-tip noise

Testing Plan

  • pnpm -C /Users/pbio/work/hyperlane-explorer run typecheck
  • pnpm -C /Users/pbio/work/hyperlane-explorer run lint
  • manual: search by recent origin tx hash and confirm near-head message appears with tip badge before finalized row
  • manual: once finalized row lands, confirm UI transitions to finalized data and badge disappears
  • manual: verify no false Failing for provisional rows (status stays pending unless delivery confirmed)

Linked PRs

Follow-up (DB indexes)

Potential scraper DB indexes if raw-query volume grows:

  • (time_updated DESC, id DESC)
  • (origin_domain, time_updated DESC, id DESC)
  • (destination_domain, time_updated DESC, id DESC)
  • sender, recipient (if address-based raw queries are introduced)

Note

Medium Risk
Adds new GraphQL query paths and merge/dedup logic that can affect search/detail results ordering and freshness, plus increases query load when hash-like inputs trigger raw fallback.

Overview
Adds a tip-stage fallback to read from raw_message_dispatch when finalized message_view rows are not yet available, enabling near-head visibility for message detail and hash-like searches; finalized rows remain authoritative when present.

Introduces parsing/typing for raw dispatch rows, merges raw+finalized results by msg_id with a scoring-based dedupe that prefers non-provisional/fresher entries, and marks raw-derived results via MessageStub.isProvisional (including a tip badge in MessageTable).

Updates server-side OG metadata fetch to fall back to raw dispatch rows, and prevents deep message debugging for provisional messages in fetchDeliveryStatus to avoid false failing states.

Written by Cursor Bugbot for commit 3713656. This will update automatically on new commits. Configure here.

@vercel
Copy link

vercel bot commented Feb 24, 2026

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

Project Deployment Actions Updated (UTC)
hyperlane-explorer Ready Ready Preview, Comment Feb 24, 2026 11:43pm

Request Review

@paulbalaji
Copy link
Collaborator Author

@claude review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 5 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@claude
Copy link

claude bot commented Feb 24, 2026

Claude finished @paulbalaji's task in 3m 19s —— View job


Review: PR #281 — tip-stage raw fallback for near-head explorer visibility

  • Read all changed files and gather context
  • Analyze code against review guidelines
  • Evaluate Cursor Bugbot findings
  • Post review feedback

Overall

Well-structured PR. The dual-source query pattern (finalized + raw fallback) is clean, and the merge/dedup approach is sound. The provisional guard in delivery status is a good call to avoid false Failing states.

The approach of keeping raw queries scoped to hash-like inputs and msg_id detail is the right tradeoff — avoids heavy table scans while covering the primary use case ("I just sent a message, where is it?").


Bugbot Assessment

Of the 5 Bugbot findings:

  • "Merging logic ignores deduplication scoring" (High)False positive. The two dedup steps serve different purposes: scoreMessage handles within-source dedup in parse.ts, while the search merge in useMessageQuery correctly does a simple finalized-wins overwrite. No issue.
  • "Raw query not enabled for message ID searches" (High)False positive. Message IDs are 0x-prefixed 66-char hex, which passes isPotentiallyTransactionHash. The raw fallback fires for msg_id searches. See comment 3 below.
  • "Unconditional raw query refresh" (Medium)Valid. See comment 1 below.
  • "Duplicate function" (Low)Valid. See comment 2 below.
  • "Scoring ignores empty string body" (Low)Not a bug. Empty string is falsy, so body: '' correctly scores 0 for the body bonus.

Inline Feedback

1. Guard reexecuteRawQuery in detail hooksrc/features/messages/queries/useMessageQuery.ts:220 (medium)

reexecuteRawQuery is called unconditionally in reExecutor, but rawQueryConfig can be null (when buildRawMessageQuery returns null for unsupported id types). While currently MessageIdentifierType.Id always produces a non-null config, calling reexecuteRawQuery when the query was paused due to !rawQueryConfig is wasteful. Guard it:

     reexecuteQuery({ requestPolicy: 'network-only' });
-    reexecuteRawQuery({ requestPolicy: 'network-only' });
-  }, [pause, isDelivered, reexecuteQuery, reexecuteRawQuery]);
+    if (rawQueryConfig) {
+      reexecuteRawQuery({ requestPolicy: 'network-only' });
+    }
+  }, [pause, isDelivered, reexecuteQuery, reexecuteRawQuery, rawQueryConfig]);

2. Remove duplicate functionsrc/features/messages/queries/build.ts:316-319 (low)

buildRawWarpRouteWhereClause is identical to buildWarpRouteWhereClause — the raw table uses the same sender/recipient column names. Reuse the existing function and delete this duplicate. On line 262, change to:

const warpRouteWhereClause = buildWarpRouteWhereClause(warpAddressesBytea);

3. Add clarifying comment for shouldQueryRawsrc/features/messages/queries/useMessageQuery.ts:97-98 (nit)

isPotentiallyTransactionHash returns true for EVM 0x-prefixed 66-char hex, which also matches Hyperlane message IDs (same format). The raw fallback does fire for msg_id searches despite the function name suggesting otherwise. A comment would prevent future confusion:

+  // msg_id shares the same hex format as tx hashes, so this also enables raw fallback for msg_id searches
   const shouldQueryRaw =
     dbStatusFilter !== 'delivered' && hasInput && isPotentiallyTransactionHash(sanitizedInput);

4. Prefer ?? over ||src/features/messages/queries/parse.ts:214 (nit)

Per project guidelines, prefer ?? over || to preserve falsy-but-valid values:

-        timestamp: parseTimestampString(m.time_updated || m.time_created),
+        timestamp: parseTimestampString(m.time_updated ?? m.time_created),

Missing Tests

No unit tests for the new parsing/scoring logic. parseRawMessageStub, parseRawMessage, scoreMessage, and deduplicateMessageList are all testable pure functions. Consider adding tests in a follow-up.

Other Minor Notes

  • Gas fields hardcoded to 0 for raw messages will display on the detail page — consider conditionally hiding the gas section when isProvisional in a follow-up
  • UI tip badge uses blue, consistent with the "blue/neutral for non-destination" guideline

@paulbalaji
Copy link
Collaborator Author

Addressed latest review feedback in 9877150:\n- raw fallback gating now explicitly supports message-id/tx-hash shape ( + 64 hex)\n- search merge now uses shared scored dedupe () instead of unconditional overwrite\n- guarded raw reexecute in detail hook\n- removed duplicate warp-route clause helper\n- switched timestamp fallback to \n\nValidation:\n-

@hyperlane-xyz/explorer@12.0.0 prettier /Users/pbio/work/hyperlane-explorer
prettier --write ./src

src/AppLayout.tsx 252ms (unchanged)
src/components/buttons/BorderedButton.tsx 26ms (unchanged)
src/components/buttons/RadioButtons.tsx 19ms (unchanged)
src/components/buttons/SolidButton.tsx 16ms (unchanged)
src/components/buttons/TextButton.tsx 13ms (unchanged)
src/components/errors/ErrorBoundary.tsx 12ms (unchanged)
src/components/icons/ChainLogo.tsx 14ms (unchanged)
src/components/icons/TokenIcon.tsx 16ms (unchanged)
src/components/layout/Card.tsx 10ms (unchanged)
src/components/nav/Footer.tsx 18ms (unchanged)
src/components/nav/Header.tsx 26ms (unchanged)
src/components/nav/InfoBanner.tsx 8ms (unchanged)
src/components/OGHead.tsx 11ms (unchanged)
src/components/search/MiniSearchBar.tsx 13ms (unchanged)
src/components/search/SearchBar.tsx 13ms (unchanged)
src/components/search/SearchFilterBar.tsx 27ms (unchanged)
src/components/search/SearchStates.tsx 14ms (unchanged)
src/consts/api.ts 7ms (unchanged)
src/consts/appMetadata.ts 9ms (unchanged)
src/consts/config.ts 8ms (unchanged)
src/consts/environments.ts 8ms (unchanged)
src/consts/links.ts 7ms (unchanged)
src/consts/tokenStandards.ts 7ms (unchanged)
src/consts/values.ts 10ms (unchanged)
src/features/api/getMessages.ts 13ms (unchanged)
src/features/api/getStatus.ts 14ms (unchanged)
src/features/api/searchMessages.ts 12ms (unchanged)
src/features/api/searchPiMessages.ts 18ms (unchanged)
src/features/api/types.ts 9ms (unchanged)
src/features/api/utils.ts 12ms (unchanged)
src/features/chains/ChainConfigSyncer.tsx 7ms (unchanged)
src/features/chains/ChainSearchModal.tsx 9ms (unchanged)
src/features/chains/MissingChainConfigToast.tsx 11ms (unchanged)
src/features/chains/queries/fragments.ts 14ms (unchanged)
src/features/chains/queries/useScrapedChains.ts 15ms (unchanged)
src/features/chains/useChainMetadata.ts 12ms (unchanged)
src/features/chains/utils.ts 13ms (unchanged)
src/features/debugger/debugMessage.ts 33ms (unchanged)
src/features/debugger/strings.ts 8ms (unchanged)
src/features/debugger/types.ts 8ms (unchanged)
src/features/deliveryStatus/fetchDeliveryStatus.ts 14ms (unchanged)
src/features/deliveryStatus/types.ts 8ms (unchanged)
src/features/deliveryStatus/useMessageDeliveryStatus.tsx 15ms (unchanged)
src/features/messages/cards/CodeBlock.tsx 9ms (unchanged)
src/features/messages/cards/CollateralCards.tsx 19ms (unchanged)
src/features/messages/cards/ContentDetailsCard.tsx 17ms (unchanged)
src/features/messages/cards/GasDetailsCard.tsx 22ms (unchanged)
src/features/messages/cards/IcaDetailsCard.tsx 13ms (unchanged)
src/features/messages/cards/IsmDetailsCard.tsx 11ms (unchanged)
src/features/messages/cards/KeyValueRow.tsx 10ms (unchanged)
src/features/messages/cards/TimelineCard.tsx 12ms (unchanged)
src/features/messages/cards/TransactionCard.tsx 23ms (unchanged)
src/features/messages/cards/types.ts 6ms (unchanged)
src/features/messages/cards/WarpRouteVisualizationCard.tsx 16ms (unchanged)
src/features/messages/cards/WarpTransferDetailsCard.tsx 17ms (unchanged)
src/features/messages/collateral/types.ts 8ms (unchanged)
src/features/messages/collateral/useActiveRebalances.ts 14ms (unchanged)
src/features/messages/collateral/useCollateralStatus.ts 14ms (unchanged)
src/features/messages/collateral/utils.ts 10ms (unchanged)
src/features/messages/ica.ts 19ms (unchanged)
src/features/messages/MessageDetails.tsx 21ms (unchanged)
src/features/messages/MessageSearch.tsx 20ms (unchanged)
src/features/messages/MessageTable.tsx 19ms (unchanged)
src/features/messages/pi-queries/fetchPiChainMessages.test.ts 16ms (unchanged)
src/features/messages/pi-queries/fetchPiChainMessages.ts 23ms (unchanged)
src/features/messages/pi-queries/usePiChainMessageQuery.ts 14ms (unchanged)
src/features/messages/placeholderMessages.ts 9ms (unchanged)
src/features/messages/queries/build.ts 21ms (unchanged)
src/features/messages/queries/encoding.ts 11ms (unchanged)
src/features/messages/queries/fragments.ts 10ms (unchanged)
src/features/messages/queries/parse.ts 19ms (unchanged)
src/features/messages/queries/serverFetch.ts 17ms (unchanged)
src/features/messages/queries/useMessageQuery.ts 19ms (unchanged)
src/features/messages/utils.ts 17ms (unchanged)
src/features/messages/warpVisualization/types.ts 9ms (unchanged)
src/features/messages/warpVisualization/useWarpRouteBalances.ts 17ms (unchanged)
src/features/messages/warpVisualization/useWarpRouteVisualization.ts 10ms (unchanged)
src/features/messages/warpVisualization/WarpRouteGraph.tsx 92ms (unchanged)
src/global.d.ts 12ms (unchanged)
src/multiProvider.ts 0ms (unchanged)
src/pages/_app.tsx 40ms (unchanged)
src/pages/_document.tsx 14ms (unchanged)
src/pages/api/og.tsx 44ms (unchanged)
src/pages/index.tsx 11ms (unchanged)
src/pages/message/[messageId].tsx 23ms (unchanged)
src/store.ts 53ms (unchanged)
src/styles/Color.ts 18ms (unchanged)
src/styles/fonts.ts 14ms (unchanged)
src/styles/global.css 30ms (unchanged)
src/styles/mediaQueries.ts 25ms (unchanged)
src/types.ts 15ms (unchanged)
src/utils/addresses.ts 18ms (unchanged)
src/utils/aleo-sdk-noop.js 17ms (unchanged)
src/utils/amount.test.ts 20ms (unchanged)
src/utils/amount.ts 29ms (unchanged)
src/utils/colorExtraction.ts 56ms (unchanged)
src/utils/explorers.ts 37ms (unchanged)
src/utils/logger.ts 14ms (unchanged)
src/utils/number.ts 18ms (unchanged)
src/utils/pino-noop.js 34ms (unchanged)
src/utils/queryParams.ts 21ms (unchanged)
src/utils/string.test.ts 16ms (unchanged)
src/utils/string.ts 23ms (unchanged)
src/utils/time.ts 10ms (unchanged)
src/utils/token.ts 8ms (unchanged)
src/utils/url.ts 7ms (unchanged)
src/utils/useScrollListener.ts 9ms (unchanged)
src/utils/warpRouteAmounts.test.ts 14ms (unchanged)
src/utils/warpRouteAmounts.ts 12ms (unchanged)
src/utils/yamlParsing.test.ts 9ms (unchanged)
src/utils/yamlParsing.ts 27ms (unchanged) (pass)

@paulbalaji
Copy link
Collaborator Author

Follow-up (clean formatting): addressed latest review feedback in 9877150.

Changes:

  • raw fallback gating now explicitly supports message-id/tx-hash shape (0x + 64 hex)
  • search merge now uses shared scored dedupe (mergeMessageStubs) instead of unconditional overwrite
  • guarded raw reexecute in detail hook
  • removed duplicate warp-route clause helper
  • switched timestamp fallback to ??

Validation:

  • pnpm -C /Users/pbio/work/hyperlane-explorer prettier (pass)

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@paulbalaji
Copy link
Collaborator Author

Addressed latest review comments in b3e85ad.

Changes:

  • dedupe tie-break now prefers fresher row when score ties (helps avoid stale raw winner)
  • raw timestamp parsing hardened against null/invalid values in both client parser and OG server parser
  • RawMessageDispatchEntry timestamp fields typed as nullable to match defensive parsing

Validation:

  • pnpm -C /Users/pbio/work/hyperlane-explorer run typecheck (pass)
  • pnpm -C /Users/pbio/work/hyperlane-explorer prettier (pass)

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@paulbalaji
Copy link
Collaborator Author

Addressed duplication comment in 4e6e1f4.

Changes:

  • Extracted shared parser to src/features/messages/queries/timestamp.ts.
  • Updated both parse.ts and serverFetch.ts to use parseTimestampMillis from that shared utility.
  • Removed duplicated local parser implementation from serverFetch.ts.

Validation:

  • pnpm prettier
  • pnpm lint

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@paulbalaji
Copy link
Collaborator Author

Addressed stale raw cache leak in f14c016.

Change:

  • In useMessageSearchQuery, rawMessageList now returns [] when shouldQueryRaw is false.
  • This prevents paused raw-query cache (rawData) from being parsed/merged when switching to filters like delivered.

Validation:

  • pnpm prettier
  • pnpm lint

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@paulbalaji
Copy link
Collaborator Author

Addressed latest unresolved callback-deps comment in 3713656.

Changes in useMessageQuery:

  • Memoized rawQueryConfig by messageId.
  • Added stable boolean hasRawQuery and used it for pause + reExecutor guard.
  • Removed rawQueryConfig object reference from useCallback deps to avoid per-render callback churn.

Validation:

  • pnpm prettier
  • pnpm lint

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