arena: WarBow leaderboard, battle feed, and warbowRank never refresh after page mount #182

Closed
opened 2026-05-08 01:16:06 +00:00 by Brouie · 8 comments
Brouie commented 2026-05-08 01:16:06 +00:00 (Migrated from gitlab.com)

tldr

warbowLb and warbowFeed state in useTimeCurveArenaModel are fetched exactly once on component mount via useEffect(..., []) and never refreshed. user can earn or lose hundreds of thousands of BP via buys / steals / revenge, but the rank display, leaderboard rows, and rivalry feed stay frozen at the values from page-load time.

found while dogfooding the WarBow PvP loop on the QA stack.

repro

  1. connect funded wallet, navigate to /timecurve/arena
  2. note "Your WarBow rank" panel value (e.g. "Unranked")
  3. perform any BP-changing action — buy with WarBow flag, attempt steal, claim flag, etc.
  4. wait for tx to land + indexer to catch up (~2-5 sec on local stack)
  5. observe: contract reads update correctly (Your BP, Top rivals podium reads, etc.) BUT:
    • "Your WarBow rank" stays "Unranked" even when BP is now well above chasing-pack threshold
    • chasing pack list keeps showing the same block-N entries from initial fetch
    • rivalry feed shows nothing new

i did 2 steals taking my BP from 286,250 → 498,017. ladder still showed me Unranked.

source

frontend/src/pages/timeCurveArena/useTimeCurveArenaModel.tsx:362-378

useEffect(() => {
  let cancelled = false;
  void (async () => {
    const [lb, fd] = await Promise.all([
      fetchTimecurveWarbowLeaderboard(12, 0),
      fetchTimecurveWarbowBattleFeed(20, 0),
    ]);
    if (cancelled) {
      return;
    }
    setWarbowLb(lb?.items ?? null);
    setWarbowFeed(fd?.items ?? null);
  })();
  return () => {
    cancelled = true;
  };
}, []);  // <- empty deps, fires once on mount

warbowRank at line 1434 derives from warbowLb.findIndex(...) so it's also stale:

const warbowRank =
  address && warbowLb
    ? warbowLb.findIndex((row) => sameAddress(row.buyer, address)) + 1 || null
    : null;

downstream consumers also affected: warbowLeaderboardRows (line 1476), the chasing pack widget mapping at line 1529.

fix shape options

(a) polling interval — setInterval re-fetch every ~5s while page is mounted, like indexer connectivity polling does elsewhere
(b) refresh-on-tx — tap into a "tx confirmed" signal and refetch leaderboard + feed after every successful local tx
(c) manual refresh button next to the leaderboard panel (lightest)

(b) is probably the right fit — the leaderboard only meaningfully changes when the connected wallet acts or the head moves, and the existing wagmi watch on chain reads would be a natural hook point.

severity

P3 polish. data still loads, just doesn't refresh. the contract-read-backed surfaces (Your BP, Top rivals from warbowLadderPodiumR) update correctly, so the player still gets accurate state from the most important indicators. but the indexer-backed surfaces drift away from reality the longer the page stays open.

not in scope

contract-read surfaces (wagmi useReadContract) refresh fine — this only affects the indexer-backed aggregates fetched via fetchTimecurveWarbowLeaderboard and fetchTimecurveWarbowBattleFeed.

cc @PlasticDigits — found during arena dogfood. not blocking, file at your pace.

## tldr `warbowLb` and `warbowFeed` state in `useTimeCurveArenaModel` are fetched exactly once on component mount via `useEffect(..., [])` and never refreshed. user can earn or lose hundreds of thousands of BP via buys / steals / revenge, but the rank display, leaderboard rows, and rivalry feed stay frozen at the values from page-load time. found while dogfooding the WarBow PvP loop on the QA stack. ## repro 1. connect funded wallet, navigate to /timecurve/arena 2. note "Your WarBow rank" panel value (e.g. "Unranked") 3. perform any BP-changing action — buy with WarBow flag, attempt steal, claim flag, etc. 4. wait for tx to land + indexer to catch up (~2-5 sec on local stack) 5. observe: contract reads update correctly (Your BP, Top rivals podium reads, etc.) BUT: - "Your WarBow rank" stays "Unranked" even when BP is now well above chasing-pack threshold - chasing pack list keeps showing the same block-N entries from initial fetch - rivalry feed shows nothing new i did 2 steals taking my BP from 286,250 → 498,017. ladder still showed me Unranked. ## source `frontend/src/pages/timeCurveArena/useTimeCurveArenaModel.tsx:362-378` ```tsx useEffect(() => { let cancelled = false; void (async () => { const [lb, fd] = await Promise.all([ fetchTimecurveWarbowLeaderboard(12, 0), fetchTimecurveWarbowBattleFeed(20, 0), ]); if (cancelled) { return; } setWarbowLb(lb?.items ?? null); setWarbowFeed(fd?.items ?? null); })(); return () => { cancelled = true; }; }, []); // <- empty deps, fires once on mount ``` `warbowRank` at line 1434 derives from `warbowLb.findIndex(...)` so it's also stale: ```tsx const warbowRank = address && warbowLb ? warbowLb.findIndex((row) => sameAddress(row.buyer, address)) + 1 || null : null; ``` downstream consumers also affected: `warbowLeaderboardRows` (line 1476), the chasing pack widget mapping at line 1529. ## fix shape options (a) polling interval — `setInterval` re-fetch every ~5s while page is mounted, like indexer connectivity polling does elsewhere (b) refresh-on-tx — tap into a "tx confirmed" signal and refetch leaderboard + feed after every successful local tx (c) manual refresh button next to the leaderboard panel (lightest) (b) is probably the right fit — the leaderboard only meaningfully changes when the connected wallet acts or the head moves, and the existing wagmi watch on chain reads would be a natural hook point. ## severity P3 polish. data still loads, just doesn't refresh. the contract-read-backed surfaces (Your BP, Top rivals from `warbowLadderPodiumR`) update correctly, so the player still gets accurate state from the most important indicators. but the indexer-backed surfaces drift away from reality the longer the page stays open. ## not in scope contract-read surfaces (wagmi useReadContract) refresh fine — this only affects the indexer-backed aggregates fetched via `fetchTimecurveWarbowLeaderboard` and `fetchTimecurveWarbowBattleFeed`. cc @PlasticDigits — found during arena dogfood. not blocking, file at your pace.
PlasticDigits commented 2026-05-09 05:06:05 +00:00 (Migrated from gitlab.com)

@Brouie Good finding - please retest after fix for #172 lands as we are redesgining the podium for warbow

@Brouie Good finding - please retest after fix for #172 lands as we are redesgining the podium for warbow
PlasticDigits commented 2026-05-09 09:23:13 +00:00 (Migrated from gitlab.com)

mentioned in commit 2526541251

mentioned in commit 2526541251c6e6948a917f9946d61a2b65c5a641
PlasticDigits commented 2026-05-09 09:23:46 +00:00 (Migrated from gitlab.com)

Implemented (merged to main, commit 2526541)

Code

  • refreshWarbowIndexerAggregates in useTimeCurveArenaModel.tsx: parallel fetch of fetchTimecurveWarbowLeaderboard(12,0) + fetchTimecurveWarbowBattleFeed(20,0), sequence guard so stale async completions cannot overwrite newer results after unmount or overlapping refreshes.
  • reportIndexerFetchAttempt when indexerBaseUrl() is set (both responses must succeed for ok).
  • Polling: while Arena is mounted and VITE_INDEXER_URL is set, ~getIndexerBackoffPollMs(5000) loop (same backoff helper as Recent buys).
  • Post-tx refresh: refetchAll() now calls void refreshWarbowIndexerAggregates(), so successful buy / Kumbaya / WarBow steal·guard·revenge·flag / runVoid receipts refresh indexer-backed rank + feed immediately (issue option b), plus polling covers other wallets (a).

Docs / invariants

  • INV-FRONTEND-182-WARBOW-IDX — docs/testing/invariants-and-business-logic.md (table row + section).
  • Cross-links: docs/frontend/timecurve-views.md, docs/testing/strategy.md, docs/testing/manual-qa-checklists.md (#182), skills/README.md, skills/play-timecurve-warbow/SKILL.md, .cursor/skills/yieldomega-guardrails/SKILL.md.

Verification run

  • frontend/: npm install + npx vitest run — 302 tests passed.

Checklist for QA

  • VITE_INDEXER_URL points at a live indexer; open /timecurve/arena.
  • Note Your WarBow rank / chasing-pack rows / rivalry feed.
  • Execute a BP-changing tx (steal, buy w/ WarBow flag, claim flag, revenge, guard).
  • After receipt + indexer lag (~2–10s local), confirm rank / chasing pack / feed update without full reload.
  • Optional: idle tab while another wallet moves the ladder — UI should catch up within one–two poll intervals (~5s base; longer if indexer backoff active).

@brouie — please verify on your QA stack when convenient; leaving this issue open until you sign off.

## Implemented (merged to `main`, commit `2526541`) ### Code - **`refreshWarbowIndexerAggregates`** in `useTimeCurveArenaModel.tsx`: parallel fetch of `fetchTimecurveWarbowLeaderboard(12,0)` + `fetchTimecurveWarbowBattleFeed(20,0)`, **sequence guard** so stale async completions cannot overwrite newer results after unmount or overlapping refreshes. - **`reportIndexerFetchAttempt`** when `indexerBaseUrl()` is set (both responses must succeed for `ok`). - **Polling:** while Arena is mounted and `VITE_INDEXER_URL` is set, **~`getIndexerBackoffPollMs(5000)`** loop (same backoff helper as Recent buys). - **Post-tx refresh:** `refetchAll()` now calls `void refreshWarbowIndexerAggregates()`, so successful **buy / Kumbaya / WarBow steal·guard·revenge·flag / `runVoid`** receipts refresh indexer-backed rank + feed immediately (issue option **b**), plus polling covers other wallets (**a**). ### Docs / invariants - **`INV-FRONTEND-182-WARBOW-IDX`** — `docs/testing/invariants-and-business-logic.md` (table row + section). - Cross-links: `docs/frontend/timecurve-views.md`, `docs/testing/strategy.md`, `docs/testing/manual-qa-checklists.md` (**#182**), `skills/README.md`, `skills/play-timecurve-warbow/SKILL.md`, `.cursor/skills/yieldomega-guardrails/SKILL.md`. ### Verification run - `frontend/`: `npm install` + `npx vitest run` — **302** tests passed. --- ### Checklist for QA - [ ] `VITE_INDEXER_URL` points at a live indexer; open `/timecurve/arena`. - [ ] Note **Your WarBow rank** / chasing-pack rows / rivalry feed. - [ ] Execute a **BP-changing** tx (steal, buy w/ WarBow flag, claim flag, revenge, guard). - [ ] After receipt + indexer lag (~2–10s local), confirm **rank / chasing pack / feed** update **without** full reload. - [ ] Optional: idle tab while **another** wallet moves the ladder — UI should catch up within **one–two** poll intervals (~5s base; longer if indexer backoff active). --- @brouie — please verify on your QA stack when convenient; leaving this issue **open** until you sign off.
Brouie commented 2026-05-11 02:40:12 +00:00 (Migrated from gitlab.com)

verified at HEAD 5ca00ec — full pass.

source spot-checks (6/6):

  • refreshWarbowIndexerAggregates callback at useTimeCurveArenaModel.tsx:190
  • sequence guard via warbowIndexerFetchSeqRef — monotonic ref, post-await check at :196-198 prevents stale overwrite
  • parallel Promise.all on fetchTimecurveWarbowLeaderboard(12,0) + fetchTimecurveWarbowBattleFeed(20,0)
  • reportIndexerFetchAttempt(ok) wired when indexerBaseUrl() is set
  • polling intervals: getIndexerBackoffPollMs(3000) at :334, getIndexerBackoffPollMs(5000) at :399
  • post-tx refresh: refetchAll chain at :1574 invokes refreshWarbowIndexerAggregates

tests:

  • vitest 307/307 pass

live walk on fresh stack with active swarm + my own wallet:

  • pre-buy state: indexer reports wallet 0xf39Fd6…2266 at battle_points_after 1750
  • executed one BUY CHARM tx (7.724 CL8Y spend, +250 BP base buy, +250 BP WarBow flag plant)
  • post-tx state without page reload: wallet context chip flipped 1750 → 2000 BP within ~3 sec
  • live buys feed updated with the new tx row
  • battle feed You hit the curve row rendered for the new buy at the new block
  • continued polling picked up subsequent swarm activity through block 165 while the page stayed open

ready for close on your side @PlasticDigits

verified at HEAD 5ca00ec — full pass. source spot-checks (6/6): - `refreshWarbowIndexerAggregates` callback at useTimeCurveArenaModel.tsx:190 - sequence guard via `warbowIndexerFetchSeqRef` — monotonic ref, post-await check at :196-198 prevents stale overwrite - parallel Promise.all on `fetchTimecurveWarbowLeaderboard(12,0)` + `fetchTimecurveWarbowBattleFeed(20,0)` - `reportIndexerFetchAttempt(ok)` wired when `indexerBaseUrl()` is set - polling intervals: `getIndexerBackoffPollMs(3000)` at :334, `getIndexerBackoffPollMs(5000)` at :399 - post-tx refresh: `refetchAll` chain at :1574 invokes `refreshWarbowIndexerAggregates` tests: - vitest 307/307 pass live walk on fresh stack with active swarm + my own wallet: - pre-buy state: indexer reports wallet `0xf39Fd6…2266` at battle_points_after 1750 - executed one BUY CHARM tx (7.724 CL8Y spend, +250 BP base buy, +250 BP WarBow flag plant) - post-tx state without page reload: wallet context chip flipped 1750 → 2000 BP within ~3 sec - live buys feed updated with the new tx row - battle feed `You hit the curve` row rendered for the new buy at the new block - continued polling picked up subsequent swarm activity through block 165 while the page stayed open ready for close on your side @PlasticDigits
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-11 08:59:08 +00:00
PlasticDigits commented 2026-05-11 09:05:08 +00:00 (Migrated from gitlab.com)

mentioned in issue #164

mentioned in issue #164
PlasticDigits commented 2026-05-13 15:13:34 +00:00 (Migrated from gitlab.com)

mentioned in commit 9816f7b191

mentioned in commit 9816f7b191699589f25c7d2514fb86a5078003eb
PlasticDigits commented 2026-05-17 13:45:30 +00:00 (Migrated from gitlab.com)

mentioned in issue #211

mentioned in issue #211
PlasticDigits commented 2026-05-19 06:26:06 +00:00 (Migrated from gitlab.com)

mentioned in issue #216

mentioned in issue #216
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#182
No description provided.