Indexer offline: frontend has no user-visible signal when :3100 is unreachable, silently spams 100+ failed requests #96
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:open_issues
agents
agent:security_audit
agent:verify
area
timecurve
arena
arena-v2
audit
block:feature_review
block:security
bug
contracts
design
devops
docs
documentation
enhancement
epic
feature
frontend
indexer
low priority
megaeth
operations
ops
performance
QA
qa checklist
referrals
removal
security
smart-contracts
tech-debt
testing
time-arena
timecurve
visual-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
code/yieldomega#96
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
When the YO indexer (
:3100) becomes unreachable mid-session, the frontend silently spams hundreds ofnet::ERR_CONNECTION_REFUSEDrequests with no user-visible indication that indexer-derived data is stale or offline. The "Indexer v1.8.0 · latest indexed block N · live" status banner that normally appears at the bottom of every route is simply gone — there is no "Indexer offline" badge, no "Reconnecting..." copy, and no stale-data warning.Repro
282eb62up (SKIP+KUMBAYA+NO_COOLDOWN), Vite on 5173, sale live, MetaMask on Chain 31337/timecurveis healthy: timer ticking, indexer status banner visible at bottom showing "Indexer v1.8.0 · latest indexed block N · live"3100 → 3100(kill the indexer port forward only, leave 8545 + 5173 alone)Observed
net::ERR_CONNECTION_REFUSEDon URLs like:http://127.0.0.1:3100/v1/timecurve/chain-timerhttp://127.0.0.1:3100/v1/timecurve/buys?limit=3&offset=0Why this matters
Indexer-derived data is mainly:
A user with degraded indexer connectivity:
At TGE this becomes a real risk:
Plus the 155+ console errors in seconds is itself concerning — aggressive polling without circuit-breaker / exponential backoff hammers the indexer once it comes back up too.
Suggested resolution options
Option A — explicit "Indexer offline" banner. When indexer fetches fail N times in a row (e.g., 3 consecutive), replace the "Indexer v1.8.0 · live" status text with "Indexer offline · retrying" in red/orange. Surface in the same footer location it currently uses.
Option B — stale-data badges on indexer-derived panels. "Recent Buys" panel shows a "Last updated Xs ago · cannot reach indexer" subtitle when fetches start failing. Same for Battle Feed, Recent fee distributions.
Option C — exponential backoff + circuit breaker. After N consecutive failures, throttle from "every block" to "every 30s, then 60s, then 120s" with auto-recovery on first success. Reduces console spam + indexer DDoS-on-recovery.
Option D — distinguish empty state from offline state in copy. "Waiting for the first buy of this round" should only show when indexer is live AND has zero rows. When indexer is offline or fetches are failing, show "Cannot reach indexer · cached data may be stale" instead.
A + C + D together is the strongest fix. Single-issue scope, but fix probably touches the indexer client / hooks layer.
Severity
MEDIUM. Not a launch-blocker (RPC-derived buys still work), but is a real user-trust gap that becomes more painful at TGE. Console pollution + unclear empty states + no offline signal stack together.
Reference
Caught during F-14 walkthrough on
/timecurveSimple. Reproducible deterministically by killing port 3100 routing.cc @PlasticDigits
mentioned in commit
1a603efd4aImplemented Options A + C + D from the issue description (merged to
mainin commit1a603ef).What changed
IndexerStatusBarshows Indexer offline · retrying (new error-toned pill)./timecurvehides the global footer, so the same pill is rendered above Recent buys on Simple.reportIndexerFetchAttempt/getIndexerBackoffPollMs— healthy intervals stay fast (e.g. ~1s chain-timer, 3s status, 5s/3s buys); after the streak opens, polls step 30s → 60s → 120s until the next success resets.getJson/fetchTimecurveChainTimernow swallow network errors asnullto avoid unhandled rejections.Docs / agent cross-links
Automated tests
frontend: `npm test -- --run` (includes new `indexerConnectivity.test.ts`) and `npm run build` — green locally.Verification checklist for @brouie
/timecurveshows Indexer v… · live (pill above Recent buys).Leaving the issue open for your sign-off.
mentioned in issue #38
mentioned in issue #100
Partial verification on
1a603ef...dd1c59aHEAD today. Items 2-6 deferred until Item 1 (baseline) is resolved.Item 1 — Baseline pill on
/timecurveSimple — FAILStack up cleanly: indexer at 127.0.0.1:3100 returning
/v1/status200 withschema_version=1.8.0,max_indexed_block=12,database_connected=true./v1/timecurve/buyspopulated with the live buy from block 12 (deployer 5 CHARM minted, 5.092 CL8Y spent, +120s, 250 BP).But on
/timecurveSimple page in the browser:class="indexer-status*") returns 0 matches on SimpleConfirmed working on
/timecurve/arenaand/timecurve/protocol: both routes render the indexer status banner correctly (live state, block number, schema version).The fix description says:
Right now Simple has neither — global footer is correctly hidden but the inline-above-Recent-buys replacement pill is not rendering. So the baseline (Item 1 of the SKILL.md) cannot be confirmed visually on Simple.
Items 2-6 — Deferred until Item 1 resolves
Without a working baseline pill on Simple, I cannot verify the offline-banner state transition (Item 3) or the empty-state copy distinguishing offline vs live-zero-rows (Item 5). Will resume once the Simple-page pill rendering is fixed.
The other gated routes (Arena, Protocol) seem to render the IndexerStatusBar correctly so the offline detection logic + backoff likely work end-to-end there. Could spot-check Items 2-6 against Arena instead if you'd prefer that path while Simple is being investigated.
cc @PlasticDigits
Pill removed from simple page to keep ux simple - its already complex, so any additional info (like indexer) will cause more user confusion beyond any benefit. closing.
@Brouie Reopening because I see items 2 thru 6 still need checking. Please skip Item 1, as noted it is intentionally removed from the Simple page as the Simple page is supposed to be kept as minimal as possible, and the indexer information is not necessary for the user under normal operating conditions.
Source-level review on
1a603ef(carried into243e2a0). Acknowledging your reopen — Item 1 skipped per your direction (pill intentionally absent on Simple to keep UX minimal).Source-level findings on items 2-5
src/lib/indexerConnectivity.ts:INDEXER_OFFLINE_FAILURE_STREAK = 3(matches "≥3 distinct wall-clock seconds" in your spec)BACKOFF_MS = [30_000, 60_000, 120_000]— exact 30s → 60s → 120s tiersMath.min(streak - 3, BACKOFF_MS.length - 1)resetIndexerConnectivityForTests()test-mode reset hook presentindexerConnectivity.test.ts: 5/5 tests PASS in 4ms (tier math + streak counting + per-second debounce + success reset + offline threshold transition).TimeCurveSimplePage.tsx:1359-1364: Three-condition gate (buyPollLastOk === trueANDlength === 0AND!isOffline) correctly limits the "Waiting for the first buy" copy to your stated case.reportIndexerFetchAttempt(true)in the success branches (Arena model line 263, Simple page line 360, hero timer line 97) resetsfailureStreakto 0, whichgetIndexerBackoffPollMsconsults — intervals return to fast on first successful poll.cc @PlasticDigits
mentioned in issue #115
live walkthrough done. items 2-5 + recovery all pass.
setup: full anvil + indexer + vite stack on the same fresh deploy as #114/#115/#101. killed indexer process
yieldomega-indexer, observed offline state on browser, restarted indexer, observed recovery without reload.item 2 (backoff working): post-kill saw ~38 indexer fetch errors spread over 6+ wall-clock seconds, NOT the 155+ in 10s spam from the original repro. polls correctly stepping into 30s/60s/120s tiers per the streak gate.
item 3 (offline state triggers): with indexer dead, Arena page shows chain-timer fallback (TIME REMAINING displays em-dash + "Loading on-chain deadline…" + "Timer pressure will appear once the sale is readable from chain."), reserve podiums show "Loading the four onchain podium categories…", LIVE BUYS panel shows leprechaun + "Could not load buys from the indexer (offline, CORS, or HTTP error). Check the indexer is running." wallet-connected reads (CL8Y balance 100m, 1 CHARM live price 1.001646 CL8Y) keep working since those go through chain RPC at 8545.
item 4 (empty vs offline distinction): confirmed the distinct copy. with indexer dead → "Could not load buys from the indexer (offline, CORS, or HTTP error). Check the indexer is running." with indexer alive + zero indexed buys → "No buys indexed yet." two genuinely different paragraphs, not just a state flag.
item 5 (recovery): restarted indexer, page transitioned back to success state without a refresh. TIME REMAINING regained digits (23:35:39 ticking on the deadline countdown), LIVE BUYS card flipped from offline copy to "No buys indexed yet." empty-state copy, footer IndexerStatusBar showed "Indexer v1.11.0 · latest indexed block 10 · live". rivalry feed even surfaced the events from earlier in the session ("WarBow burn — You paid the PvP burn" + "WarBow defense — You activated guard" at block 10) once indexing caught up.
item 6 (visual confirmation): confirmed on /timecurve (Simple, IndexerStatusBar correctly hidden per your intentional design — fallback copy in Recent buys panel does the work) and /timecurve/arena (full IndexerStatusBar in footer + chain-timer fallback). Protocol path uses the same global IndexerStatusBar so behavior is shared.
item 1 skipped per your reopen note (Simple page pill intentionally absent to keep ux minimal). all 5 remaining items pass.
mentioned in issue #231
mentioned in issue #234
mentioned in issue #258
mentioned in issue #262
mentioned in issue #263
mentioned in merge request !414
mentioned in issue #342
mentioned in issue #343
mentioned in issue #354
mentioned in merge request !522