Revert "fix: manually surface LIP-118 poll while subgraph indexing is behind"#753
Conversation
… behind …" This reverts commit 29fd3cc.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Reverts the prior stopgap that manually surfaced governance polls and tolerated subgraph outages, returning the voting UI/data flow to relying solely on the subgraph now that indexing has recovered.
Changes:
- Remove manual poll merging/fallback logic from voting list/detail pages and layout poll counts.
- Delete the
constants/manualPolls.tsstopgap and related UI messaging. - Restore original
getTotalStakebehavior by removing the try/catch degradation path.
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 |
|---|---|
| pages/voting/index.tsx | Removes manual poll merging and re-gates poll list hydration on subgraph data presence. |
| pages/voting/[poll].tsx | Removes manual poll fallback for detail view and associated “indexing behind” messaging. |
| lib/api/polls.ts | Removes graceful fallback when ProtocolByBlock query fails (reverting to throw-on-error behavior). |
| layouts/main.tsx | Removes manual poll merging from active poll count computation. |
| constants/manualPolls.ts | Deletes the temporary manual poll definitions and merge helpers. |
Comments suppressed due to low confidence (1)
layouts/main.tsx:186
pollData?.polls.filter(...)can throw whenpollDatais undefined, because optional chaining only applies topollDataand not topolls. This will break the layout before the polls query resolves.
const totalActivePolls = useMemo(
() =>
pollData?.polls.filter(
(p) =>
(currentRound?.currentL1Block ?? Number.MAX_VALUE) <=
parseInt(p.endBlock)
).length,
[currentRound?.currentL1Block, pollData]
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| useEffect(() => { | ||
| const init = async () => { | ||
| // Fall back to a manually-listed poll when the subgraph doesn't have it | ||
| // (e.g. created while indexing was down), so it stays viewable/votable. | ||
| const source = data?.poll ?? getManualPoll(pollId); | ||
| if (source && currentRound?.currentL1Block) { | ||
| if (data && currentRound?.currentL1Block) { | ||
| const response = await getPollExtended( | ||
| source, | ||
| data.poll, | ||
| currentRound.currentL1Block | ||
| ); | ||
| setPollData(response); | ||
| } |
| if (pollError) { | ||
| return <FourZeroFour />; | ||
| } |
| useEffect(() => { | ||
| // Render as soon as the (on-chain) current round is available, even if the | ||
| // subgraph query returned nothing, so manually-listed polls still show while | ||
| // indexing is down. mergeManualPolls tolerates `data` being undefined. | ||
| if (currentRound?.currentL1Block) { | ||
| if (data && currentRound?.currentL1Block) { | ||
| const init = async () => { | ||
| setPolls( | ||
| await Promise.all( | ||
| mergeManualPolls(data?.polls).map((p) => | ||
| (data?.polls ?? []).map((p) => | ||
| getPollExtended(p, currentRound.currentL1Block) | ||
| ) | ||
| ) |
📝 WalkthroughWalkthroughThe manual poll stopgap and its consumers were removed. Voting pages and active poll counts now use subgraph results directly, while ChangesPoll data flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install timed out. The project may have too many dependencies for the sandbox. 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: 3
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-67: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winAdd a shared failure path for asynchronous poll initialization. Poll enrichment can throw during IPfs fetch, GraphQL stake lookup, L2 range lookup, or estimated end time calculation, but
pages/voting/index.tsxleaves the spinner rendering andpages/voting/[poll].tsxfalls back to the null-Poll spinner state. Handle rejected initialization withtry/catch/finallyand render an explicit, recoverable error state.🤖 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 - 67, Add a shared recoverable error state for asynchronous poll initialization in pages/voting/index.tsx and pages/voting/[poll].tsx: wrap getPollExtended initialization in try/catch/finally, record the failure, and always clear loading in finally so rejected enrichment cannot leave the spinner indefinitely. Update the corresponding rendering paths to show an explicit error state with a recovery action instead of the null-Poll spinner fallback; apply these changes at pages/voting/index.tsx lines 53-67 and pages/voting/[poll].tsx lines 91-102.
🤖 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 172-190: Restore error handling in getTotalStake around the Apollo
client.query call so network or GraphQL failures are caught, logged using the
existing logging mechanism, and result in undefined rather than rejecting.
Preserve the current successful response behavior and ensure getPollExtended
continues to degrade gracefully when the subgraph is unavailable.
In `@pages/voting/`[poll].tsx:
- Around line 104-105: Update the error handling in the poll page around
pollError so only a confirmed missing poll—poll === null after a successful
fetch or an explicitly missing-poll GraphQL error—renders FourZeroFour. Route
network, server, gateway, and other execution failures to the existing
error/retry state, preserving normal rendering for successful polls.
- Around line 93-98: Guard the getPollExtended call so it runs only when
data?.poll is non-null and currentRound.currentL1Block is available. In the
successful-query branch where data.poll is missing, follow the existing 404
response path instead of constructing poll data from null.
---
Outside diff comments:
In `@pages/voting/index.tsx`:
- Around line 53-67: Add a shared recoverable error state for asynchronous poll
initialization in pages/voting/index.tsx and pages/voting/[poll].tsx: wrap
getPollExtended initialization in try/catch/finally, record the failure, and
always clear loading in finally so rejected enrichment cannot leave the spinner
indefinitely. Update the corresponding rendering paths to show an explicit error
state with a recovery action instead of the null-Poll spinner fallback; apply
these changes at pages/voting/index.tsx lines 53-67 and pages/voting/[poll].tsx
lines 91-102.
🪄 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: 44f59002-d2f9-48a1-9751-01f960a24544
📒 Files selected for processing (5)
constants/manualPolls.tslayouts/main.tsxlib/api/polls.tspages/voting/[poll].tsxpages/voting/index.tsx
💤 Files with no reviewable changes (1)
- constants/manualPolls.ts
| const getTotalStake = async (l2BlockNumber?: number | undefined) => { | ||
| try { | ||
| const client = getApollo(); | ||
|
|
||
| const protocolResponse = await client.query< | ||
| ProtocolByBlockQuery, | ||
| ProtocolByBlockQueryVariables | ||
| >({ | ||
| query: ProtocolByBlockDocument, | ||
| variables: l2BlockNumber | ||
| ? { | ||
| block: { | ||
| number: l2BlockNumber, | ||
| }, | ||
| } | ||
| : undefined, | ||
| fetchPolicy: "network-only", | ||
| }); | ||
|
|
||
| return protocolResponse?.data?.protocol?.totalActiveStake; | ||
| } 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; | ||
| } | ||
| const client = getApollo(); | ||
|
|
||
| const protocolResponse = await client.query< | ||
| ProtocolByBlockQuery, | ||
| ProtocolByBlockQueryVariables | ||
| >({ | ||
| query: ProtocolByBlockDocument, | ||
| variables: l2BlockNumber | ||
| ? { | ||
| block: { | ||
| number: l2BlockNumber, | ||
| }, | ||
| } | ||
| : undefined, | ||
| fetchPolicy: "network-only", | ||
| }); | ||
|
|
||
| return protocolResponse?.data?.protocol?.totalActiveStake; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Unguarded Apollo query — subgraph failures now propagate unhandled.
getTotalStake no longer catches failures from client.query. With Apollo's default errorPolicy: "none", a network/GraphQL error rejects the promise, and since getPollExtended awaits this with no try/catch either, any subgraph hiccup will now throw through the poll rendering path instead of degrading gracefully (previously logged + returned undefined). Given that subgraph instability is exactly what motivated the original manual-poll workaround this PR reverts, losing this safety net reintroduces the failure mode it was built to avoid.
If reverting to pre-#734 behavior is intentional, worth confirming the team is fine with poll pages failing outright on subgraph errors rather than degrading.
🛡️ Optional fix to restore graceful degradation
const getTotalStake = async (l2BlockNumber?: number | undefined) => {
const client = getApollo();
- const protocolResponse = await client.query<
- ProtocolByBlockQuery,
- ProtocolByBlockQueryVariables
- >({
- query: ProtocolByBlockDocument,
- variables: l2BlockNumber
- ? {
- block: {
- number: l2BlockNumber,
- },
- }
- : undefined,
- fetchPolicy: "network-only",
- });
-
- return protocolResponse?.data?.protocol?.totalActiveStake;
+ try {
+ const protocolResponse = await client.query<
+ ProtocolByBlockQuery,
+ ProtocolByBlockQueryVariables
+ >({
+ query: ProtocolByBlockDocument,
+ variables: l2BlockNumber
+ ? {
+ block: {
+ number: l2BlockNumber,
+ },
+ }
+ : undefined,
+ fetchPolicy: "network-only",
+ });
+
+ return protocolResponse?.data?.protocol?.totalActiveStake;
+ } catch (err) {
+ console.warn(`Could not fetch total stake: ${err}`);
+ return undefined;
+ }
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const getTotalStake = async (l2BlockNumber?: number | undefined) => { | |
| try { | |
| const client = getApollo(); | |
| const protocolResponse = await client.query< | |
| ProtocolByBlockQuery, | |
| ProtocolByBlockQueryVariables | |
| >({ | |
| query: ProtocolByBlockDocument, | |
| variables: l2BlockNumber | |
| ? { | |
| block: { | |
| number: l2BlockNumber, | |
| }, | |
| } | |
| : undefined, | |
| fetchPolicy: "network-only", | |
| }); | |
| return protocolResponse?.data?.protocol?.totalActiveStake; | |
| } 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; | |
| } | |
| const client = getApollo(); | |
| const protocolResponse = await client.query< | |
| ProtocolByBlockQuery, | |
| ProtocolByBlockQueryVariables | |
| >({ | |
| query: ProtocolByBlockDocument, | |
| variables: l2BlockNumber | |
| ? { | |
| block: { | |
| number: l2BlockNumber, | |
| }, | |
| } | |
| : undefined, | |
| fetchPolicy: "network-only", | |
| }); | |
| return protocolResponse?.data?.protocol?.totalActiveStake; | |
| const getTotalStake = async (l2BlockNumber?: number | undefined) => { | |
| const client = getApollo(); | |
| try { | |
| const protocolResponse = await client.query< | |
| ProtocolByBlockQuery, | |
| ProtocolByBlockQueryVariables | |
| >({ | |
| query: ProtocolByBlockDocument, | |
| variables: l2BlockNumber | |
| ? { | |
| block: { | |
| number: l2BlockNumber, | |
| }, | |
| } | |
| : undefined, | |
| fetchPolicy: "network-only", | |
| }); | |
| return protocolResponse?.data?.protocol?.totalActiveStake; | |
| } catch (err) { | |
| console.warn(`Could not fetch total stake: ${err}`); | |
| return undefined; | |
| } | |
| }; |
🤖 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 172 - 190, Restore error handling in
getTotalStake around the Apollo client.query call so network or GraphQL failures
are caught, logged using the existing logging mechanism, and result in undefined
rather than rejecting. Preserve the current successful response behavior and
ensure getPollExtended continues to degrade gracefully when the subgraph is
unavailable.
| if (data && currentRound?.currentL1Block) { | ||
| const response = await getPollExtended( | ||
| source, | ||
| data.poll, | ||
| currentRound.currentL1Block | ||
| ); | ||
| setPollData(response); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate files =="
fd -a 'poll.*\.(ts|tsx)$|voting.*\.(ts|tsx)$' . | sed 's#^\./##' | head -100
echo "== target file outline/size =="
wc -l pages/voting/[poll].tsx 2>/dev/null || true
ast-grep outline "pages/voting/[poll].tsx" 2>/dev/null || true
echo "== relevant lines =="
cat -n "pages/voting/[poll].tsx" | sed -n '1,180p'
echo "== getPollExtended usages/definition =="
rg -n "function getPollExtended|const getPollExtended|getPollExtended\(" . -SRepository: livepeer/explorer
Length of output: 7063
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate source without relying on exact path
echo "== git ls files matching poll_extended or polling =="
git ls-files | rg -i 'poll|extended' | head -120
echo "== search api call helpers =="
rg -n "getPollExtended|poll_extended|extended" . -S | head -200Repository: livepeer/explorer
Length of output: 1755
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== lib/api/polls.ts =="
cat -n lib/api/polls.ts | sed -n '1,180p'
echo "== apollo schema/types around poll =="
cat -n apollo/createSchema.ts | sed -n '1,180p'
rg -n -C 4 "poll|Poll|id: ID|status: PollStatus|currentL1Block" apollo queries lib/api pages/voting -S | head -260
echo "== all uses of pollData attributes/status =="
cat -n pages/voting/[poll].tsx | sed -n '180,430p'Repository: livepeer/explorer
Length of output: 32155
🌐 Web query:
GraphQL Livepeer subgraph Poll id query poll(id: $id): Poll generated schema optional id argument
💡 Result:
In the Livepeer subgraph schema, the Poll entity is defined with an ID field of type ID! [1]. When querying individual entities in a GraphQL subgraph, you can typically retrieve a specific entity by its ID using the singular form of the entity name as a query field (e.g., poll(id: $id): Poll) [2][3]. Since the Poll entity's id field is marked as non-nullable (ID!) [1], the query input for retrieving a single poll by its ID requires an ID argument. While the schema definition itself specifies the ID field's requirements, the generated GraphQL schema for The Graph subgraphs includes top-level query fields for each entity, which natively support fetching by the entity's ID [1][2]. If you are writing a query to fetch a specific poll, you would structure it as follows: query GetPoll($id: String!) { poll(id: $id) { id proposal endBlock quorum quota tally { id } } } In this structure, the id argument is required to identify the specific Poll entity [1][2]. If you are encountering issues where the id argument appears to be optional or missing in certain documentation or generated types, ensure that your client-side GraphQL generator (e.g., Apollo Client) is correctly pointing to the latest subgraph schema, as entity-specific query fields for objects with ID types are standard for subgraphs [2][3].
Citations:
- 1: https://github.com/livepeer/subgraph/blob/main/schema.graphql
- 2: feat: enhance polling functionality with voting transparency on the governance page #683
- 3: https://github.com/livepeer/explorer/blob/main/apollo/subgraph.ts
🌐 Web query:
The Graph generated singular entity query poll(id) null response when id not found
💡 Result:
In The Graph, a singular entity query (e.g., entity(id: "123")) returns a standard GraphQL response where the data field for that entity is null if the requested ID is not found [1][2]. When this occurs, the response typically follows this structure: { "data": { "entityName": null }, "errors": [ { "message": "Entity not found", "locations": [{"line": 2, "column": 3}], "path": ["entityName"] } ] } Key technical points: 1. Expected Behavior: Returning null for a missing entity is the standard GraphQL behavior for nullable singular query fields [1][2][3]. 2. Error Response: While the data value is null, The Graph often includes an entry in the errors array explicitly stating "Entity not found" [1][2]. 3. Troubleshooting: If you are encountering unexpected null results when you believe an entity should exist, verify that the id string passed in the query exactly matches the ID stored in the subgraph database [4]. Common issues include: - Case sensitivity (e.g., "0x0b" vs "0xb"). - Incorrect padding or formatting of Hex/Bytes IDs [4]. - Synchronization delays where the entity has not yet been indexed [4]. If you are seeing a null response without an error, or if you suspect it should be returning an object, double-check your ID generation logic in your subgraph's mapping code to ensure it aligns perfectly with the queries you are performing [4].
Citations:
- 1: https://mintlify.wiki/graphprotocol/graph-node/api/graphql-api
- 2: https://graphprotocol-graph-node-30.mintlify.app/api/graphql-api
- 3: https://github.com/graphql/graphql-spec/blob/main/spec/Section%203%20--%20Type%20System.md
- 4: Local subgraph returning null graphprotocol/graph-node#2633
🌐 Web query:
site:github.com/livepeer/explorer apollo/subgraph.ts generated types usePollQuery poll data
💡 Result:
The file apollo/subgraph.ts in the livepeer/explorer repository is an auto-generated file containing Apollo Client types and hooks derived from GraphQL operations [1][2]. It is not a library-provided file but rather a product of code generation, typically using a tool like GraphQL Code Generator [1]. In this context, usePollQuery is a generated React hook that corresponds to a GraphQL query defined (usually in a.graphql file) within the project [2]. When you define a query named Poll (e.g., in a file like queries/poll.graphql), the code generation process automatically creates a corresponding React hook named usePollQuery [2]. To work with poll data via this hook: 1. Identification: The hook is generated based on the name of the operation in your.graphql files. If your operation is named query Poll, the generated hook will be usePollQuery [2]. 2. Usage: You can use this hook in your React components to fetch poll data from the subgraph [2]. It returns an object containing the data, loading state, error state, and other standard Apollo Client hook properties [2]. 3. Generation: Because apollo/subgraph.ts is auto-generated, you should not edit it directly [1]. Instead, update the underlying GraphQL query files (e.g., in the queries/ directory) and re-run your generation command (often configured in codegen.yml) to update the types and hooks [1][2]. If you are trying to access specific poll data, ensure that your GraphQL query explicitly requests the necessary fields (such as poll ID, choiceID, or voter details) and that your codegen configuration is set to output React hooks [2].
Citations:
- 1: https://github.com/livepeer/explorer/blob/main/apollo/subgraph.ts
- 2: feat: enhance polling functionality with voting transparency on the governance page #683
Guard data.poll before calling getPollExtended.
- Problem:
datacan be successfully populated withdata.poll === null, but this still callsgetPollExtended(null, ...), which builds aPollExtendedfromnulland can render a poll page withstatus === "active"andendBlock === 0. - Why it matters: Unknown/missing poll IDs bypass the existing 404 path and show malformed poll data instead.
- Suggested fix: Only call
getPollExtendedwhendata?.pollis non-null; otherwise return 404 for a successful query with no matching poll.
🤖 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 93 - 98, Guard the getPollExtended call
so it runs only when data?.poll is non-null and currentRound.currentL1Block is
available. In the successful-query branch where data.poll is missing, follow the
existing 404 response path instead of constructing poll data from null.
| if (pollError) { | ||
| return <FourZeroFour />; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## files"
git ls-files | sed -n '1,120p'
echo
echo "## target outline/size"
wc -l pages/voting/\[poll\].tsx 2>/dev/null || true
ast-grep outline 'pages/voting/[poll].tsx' 2>/dev/null || true
echo
echo "## target lines 1-180"
cat -n 'pages/voting/[poll].tsx' | sed -n '1,180p'
echo
echo "## hunt for FourZeroFour and pollError usages"
rg -n "FourZeroFour|pollError|apolloError|networkError|graphQLErrors|NotFoundError|Poll" . --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,200p'Repository: livepeer/explorer
Length of output: 39702
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## files"
git ls-files | sed -n '1,120p'
echo
echo "## target outline/size"
wc -l 'pages/voting/[poll].tsx' 2>/dev/null || true
ast-grep outline 'pages/voting/[poll].tsx' 2>/dev/null || true
echo
echo "## target lines 1-180"
cat -n 'pages/voting/[poll].tsx' | sed -n '1,180p'
echo
echo "## hunt for FourZeroFour and pollError usages"
rg -n "FourZeroFour|pollError|apolloError|networkError|graphQLErrors|NotFoundError|Poll" . --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,200p'Repository: livepeer/explorer
Length of output: 39584
Do not map every Apollo error to 404.
- Problem:
pollErrorcan be a network failure, server/gateway error, or execution error, but this branch returns<FourZeroFour />for all of them. - Why it matters: Subgraph or network outages make a valid poll URL look like “not found,” hiding recoverable failures from the user.
- Suggested fix: Only show 404 for
poll === nullafter a successful fetch, or for GraphQL errors that explicitly represent a missing poll. Render an error/retry state for network and other non-missing-entity failures.
🤖 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 104 - 105, Update the error handling in
the poll page around pollError so only a confirmed missing poll—poll === null
after a successful fetch or an explicitly missing-poll GraphQL error—renders
FourZeroFour. Route network, server, gateway, and other execution failures to
the existing error/retry state, preserving normal rendering for successful
polls.
… behind …" (livepeer#753) This reverts commit 29fd3cc now the new subgraph is fully indexed.
… behind …" (livepeer#753) This reverts commit 29fd3cc now the new subgraph is fully indexed.
Reverts #734 now the new subgraph is indexed again.
Summary by CodeRabbit