fix: manually surface LIP-118 poll while subgraph indexing is behind - #734
Conversation
The voting list/detail pages and the Governance nav badge source polls entirely from the subgraph, so a poll created during an indexing halt (LIP-118 "Delegated Reward Calling") doesn't appear and can't be reached for voting through the UI — even though voting itself is an on-chain action. Add a temporary MANUAL_POLLS constant (subgraph-shaped entries) merged into the voting list, the poll detail page, and the active-poll nav count, deduped by poll address so the real tallied copy wins and the manual entry drops out once indexing recovers. Manual polls carry no tally, so the detail page shows a "counts not loaded" note; casting votes works normally on-chain. getTotalStake now degrades gracefully if the subgraph is unavailable so manual polls still render. Revert once the subgraph has reindexed the poll. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR adds a temporary “manual poll” fallback so a governance poll (LIP-118) remains discoverable and votable in the UI when the subgraph is behind or unavailable, and self-cleans once indexing catches up.
Changes:
- Introduces
constants/manualPolls.tswith a manually-seeded poll list and helpers to merge/dedupe against subgraph results. - Updates voting list/detail pages to fall back to manual entries when the subgraph doesn’t return the poll.
- Makes the Governance badge count include manual polls, and makes total stake fetching degrade gracefully during subgraph outages.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
constants/manualPolls.ts |
Adds manual poll entries plus merge/lookup helpers for subgraph-shaped polls. |
pages/voting/index.tsx |
Merges manual polls into the voting list, rendering once on-chain round data is available. |
pages/voting/[poll].tsx |
Adds manual fallback on poll detail, avoids 404 for manual polls, and displays an indexing/tally note. |
layouts/main.tsx |
Includes manual polls in the active-poll badge count. |
lib/api/polls.ts |
Wraps total stake subgraph query in try/catch to allow rendering during outages. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,58 @@ | |||
| import { PollsQuery } from "apollo"; | |||
| // Only 404 on a genuine subgraph error with no manual fallback for this poll. | ||
| if (pollError && !getManualPoll(pollId)) { | ||
| return <FourZeroFour />; | ||
| } |
| } catch (err) { | ||
| // The subgraph can be unavailable (e.g. an indexing halt). Total stake only | ||
| // feeds participation percentages, so degrade gracefully instead of failing | ||
| // the whole poll render — this keeps manually-listed polls viewable/votable. | ||
| const detail = err instanceof Error ? err.message : String(err); | ||
| console.warn(`Could not fetch total stake from subgraph (${detail})`); | ||
| return undefined; | ||
| } |
📝 WalkthroughWalkthroughAdds manually configured governance poll fallbacks, integrates them into voting views and active-poll counts, and makes total active stake retrieval return ChangesManual poll fallback
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant VotingIndex
participant ManualPollHelpers
participant PollData
participant GetPollExtended
VotingIndex->>PollData: read subgraph polls
VotingIndex->>ManualPollHelpers: mergeManualPolls(data?.polls)
ManualPollHelpers-->>VotingIndex: merged poll list
VotingIndex->>GetPollExtended: load polls at current L1 block
GetPollExtended-->>VotingIndex: extended poll data
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pages/voting/index.tsx (1)
53-71: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winFix async
useEffectrace conditions.Problem: The async
init()calls inside these effects do not discard stale responses. Whendataupdates (e.g., from undefined to populated), a secondinit()triggers. If the network requests for the newer execution complete before the older one, the older fallback-driven response will erroneously overwrite the final state.
Why it matters: Network latency variations can cause the page to revert to displaying only the manual fallback data, hiding real active polls even when the subgraph is fully operational.
Suggested fix: Implement a localignoreflag to discard stale closures.
pages/voting/index.tsx#L53-L71: Set anlet ignore = false;variable, only update stateif (!ignore), and return() => { ignore = true; }.pages/voting/[poll].tsx#L92-L106: Apply the sameignoreflag pattern to prevent stalepollDatastate overwrites.As per path instructions, ensure correctness and realistic production edge cases.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pages/voting/index.tsx` around lines 53 - 71, The asynchronous initialization in the useEffect at pages/voting/index.tsx:53-71 can overwrite newer results with stale responses; add a local ignore flag, guard setPolls and setLoading with it, and return cleanup that marks the effect stale. Apply the same ignore-flag cleanup and guarded state update to the pollData effect at pages/voting/[poll].tsx:92-106.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/api/polls.ts`:
- Around line 196-199: Update getPollExtended to guard both
totalParticipationPercent and nonVotersPercent calculations against totalStake
being zero or otherwise non-positive, returning 0 instead of performing division
in that fallback case. Preserve the existing percentage calculations when
totalStake is greater than zero.
In `@pages/voting/`[poll].tsx:
- Around line 220-236: The manual-poll fallback warning currently renders
whenever the poll ID is in the hardcoded list, even when indexed subgraph data
is available. Update the condition around the warning Box to require both an
absent data?.poll result and isManualPoll(pollData.id), preserving the warning
only while fallback data is actively used.
---
Outside diff comments:
In `@pages/voting/index.tsx`:
- Around line 53-71: The asynchronous initialization in the useEffect at
pages/voting/index.tsx:53-71 can overwrite newer results with stale responses;
add a local ignore flag, guard setPolls and setLoading with it, and return
cleanup that marks the effect stale. Apply the same ignore-flag cleanup and
guarded state update to the pollData effect at pages/voting/[poll].tsx:92-106.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: ebec1646-2287-4c3a-8a2f-0edb91537d2d
📒 Files selected for processing (5)
constants/manualPolls.tslayouts/main.tsxlib/api/polls.tspages/voting/[poll].tsxpages/voting/index.tsx
| const detail = err instanceof Error ? err.message : String(err); | ||
| console.warn(`Could not fetch total stake from subgraph (${detail})`); | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Prevent downstream division by zero when the fallback triggers.
Problem: Returning undefined here causes totalStake to evaluate to 0 in getPollExtended. This results in division by zero (0/0 or votes/0), generating NaN or Infinity for the participation percentages.
Why it matters: The graceful degradation introduces visual bugs (e.g., NaN%) instead of a clean fallback.
Suggested fix: Ensure getPollExtended safeguards against totalStake === 0 when calculating totalParticipationPercent and nonVotersPercent (e.g., totalStake > 0 ? totalVoteStake / totalStake : 0).
As per path instructions, check for correctness and runtime issues.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/api/polls.ts` around lines 196 - 199, Update getPollExtended to guard
both totalParticipationPercent and nonVotersPercent calculations against
totalStake being zero or otherwise non-positive, returning 0 instead of
performing division in that fallback case. Preserve the existing percentage
calculations when totalStake is greater than zero.
Source: Path instructions
| {isManualPoll(pollData.id) && ( | ||
| <Box | ||
| css={{ | ||
| marginBottom: "$3", | ||
| padding: "$3", | ||
| borderRadius: "$3", | ||
| border: "1px solid $blue5", | ||
| backgroundColor: "$blue2", | ||
| }} | ||
| > | ||
| <Text size="1" css={{ color: "$blue11" }}> | ||
| Live vote counts aren't available for this poll yet — | ||
| the subgraph is still indexing it. The totals below may read | ||
| 0 until indexing catches up. Voting works normally. | ||
| </Text> | ||
| </Box> | ||
| )} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Ensure the fallback warning only displays when the fallback is actively used.
Problem: isManualPoll(pollData.id) evaluates to true if the ID exists in the hardcoded list, even after data?.poll successfully loads and replaces the fallback with real indexed subgraph data.
Why it matters: Users will see real vote totals alongside a confusing warning stating that vote counts are not yet available.
Suggested fix: Update the condition to check that the subgraph query actually failed to provide the poll, for example: {!data?.poll && isManualPoll(pollData.id)}.
As per path instructions, focus on correctness and real bugs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pages/voting/`[poll].tsx around lines 220 - 236, The manual-poll fallback
warning currently renders whenever the poll ID is in the hardcoded list, even
when indexed subgraph data is available. Update the condition around the warning
Box to require both an absent data?.poll result and isManualPoll(pollData.id),
preserving the warning only while fallback data is actively used.
Source: Path instructions
…h resyncs (#749) Stopgap while the subgraph resyncs from scratch (livepeer/subgraph#247), which leaves the manually surfaced LIP-118 poll showing 0% support and 0% participation even for people who already voted. Revert this and #734 once the subgraph has reindexed the poll. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot <copilot-pull-request-reviewer[bot]@users.noreply.github.com>
…ivepeer#734) The voting list/detail pages and the Governance nav badge source polls entirely from the subgraph, so a poll created during an indexing halt (LIP-118 "Delegated Reward Calling") doesn't appear and can't be reached for voting through the UI — even though voting itself is an on-chain action. Add a temporary MANUAL_POLLS constant (subgraph-shaped entries) merged into the voting list, the poll detail page, and the active-poll nav count, deduped by poll address so the real tallied copy wins and the manual entry drops out once indexing recovers. Manual polls carry no tally, so the detail page shows a "counts not loaded" note; casting votes works normally on-chain. getTotalStake now degrades gracefully if the subgraph is unavailable so manual polls still render. Revert once the subgraph has reindexed the poll. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h resyncs (livepeer#749) Stopgap while the subgraph resyncs from scratch (livepeer/subgraph#247), which leaves the manually surfaced LIP-118 poll showing 0% support and 0% participation even for people who already voted. Revert this and livepeer#734 once the subgraph has reindexed the poll. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot <copilot-pull-request-reviewer[bot]@users.noreply.github.com>
…ivepeer#734) The voting list/detail pages and the Governance nav badge source polls entirely from the subgraph, so a poll created during an indexing halt (LIP-118 "Delegated Reward Calling") doesn't appear and can't be reached for voting through the UI — even though voting itself is an on-chain action. Add a temporary MANUAL_POLLS constant (subgraph-shaped entries) merged into the voting list, the poll detail page, and the active-poll nav count, deduped by poll address so the real tallied copy wins and the manual entry drops out once indexing recovers. Manual polls carry no tally, so the detail page shows a "counts not loaded" note; casting votes works normally on-chain. getTotalStake now degrades gracefully if the subgraph is unavailable so manual polls still render. Revert once the subgraph has reindexed the poll.
…h resyncs (livepeer#749) Stopgap while the subgraph resyncs from scratch (livepeer/subgraph#247), which leaves the manually surfaced LIP-118 poll showing 0% support and 0% participation even for people who already voted. Revert this and livepeer#734 once the subgraph has reindexed the poll. --------- Co-authored-by: Copilot <copilot-pull-request-reviewer[bot]@users.noreply.github.com>
Summary
A governance poll created while the subgraph was behind on indexing — LIP-118 "Delegated Reward Calling" (creation tx), doesn't show up on the voting pages or in the Governance nav badge, because those are sourced entirely from the subgraph. Voting itself is an on-chain action, but the poll was unreachable through the UI.
This adds a temporary manual entry so the poll is listed and votable during the outage. It self-cleans once the subgraph reindexes the poll.
What changed
constants/manualPolls.ts(new) —MANUAL_POLLS(subgraph-shaped entries) seeded with LIP-118, plusmergeManualPolls/getManualPoll/isManualPollhelpers. Merges are deduped by poll address, so the real subgraph copy wins once indexed and the manual entry drops out automatically.pages/voting/index.tsx— merge manual polls into the list; render as soon as the on-chain current round is available (so the list shows even if the subgraph query returns nothing).pages/voting/[poll].tsx— fall back to the manual entry on the detail page; don't 404 when a manual entry exists; show a blue "counts not loaded" note above the tally for manual polls.layouts/main.tsx— include manual polls in the active-poll count so the green Governance badge reflects the live LIP.lib/api/polls.ts—getTotalStakedegrades gracefully (returnsundefined) if the subgraph is unavailable, so a manual poll still renders during a full outage.Behavior
Poll.vote), and the eligibility gate reads on-chain pending stake, not the subgraph.Reverting
Delete
constants/manualPolls.tsand its imports (or just empty theMANUAL_POLLSarray) once the subgraph has reindexed the poll.Testing
pnpm typecheck+pnpm lintpass.Summary by CodeRabbit
New Features
Bug Fixes