Skip to content

feat: enhance polling functionality with voting transparency on the governance page#683

Open
ibsule wants to merge 3 commits into
livepeer:mainfrom
ibsule:governance-vote-transparency
Open

feat: enhance polling functionality with voting transparency on the governance page#683
ibsule wants to merge 3 commits into
livepeer:mainfrom
ibsule:governance-vote-transparency

Conversation

@ibsule
Copy link
Copy Markdown

@ibsule ibsule commented May 27, 2026

Description

This PR introduces a new poll vote transparency experience on the governance voting page through the following:

  • Adds a full PollVote UI flow (desktop/mobile vote tables, vote detail/popover views) so users can inspect who voted and how much stake was used.
  • Expands polling data/query coverage to include richer vote-event fields and wiring needed to render detailed vote data.
  • Updates the voting page/widget UX to make navigating between overview and vote details easier.

Why

Governance users need transparent, in-context access to vote details (who voted, vote stake, and related metadata) to better understand outcomes and build trust in the polling process without leaving the poll flow.

Type of Change

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation update
  • style: Code style/formatting changes (no logic changes)
  • refactor: Code refactoring (no behavior change)
  • perf: Performance improvement
  • test: Adding or updating tests
  • build: Build system or dependency changes
  • ci: CI/CD changes
  • chore: Other changes

Related Issue(s)

Related: #310
Closes: #482

Changes Made

  • Added a new PollVote feature set for deeper vote visibility on governance pages.
  • Introduced new components: PollVoteDetail, DesktopVoteTable, MobileVoteView, MobileVoteCards, PollVotePopover, and components/PollVote/index.tsx.
  • Updated data layer and queries (apollo/subgraph.ts, lib/api/polls.ts, queries/poll.graphql, new queries/voteEvents.graphql) to fetch richer vote details.
  • Enhanced pages/voting/[poll].tsx and components/PollVotingWidget/index.tsx to support improved vote UX/navigation.

Testing

  • Tested locally
  • Added/updated tests
  • All tests passing

Impact / Risk

Risk level: Low

Impacted areas: UI

Rollback plan: PR revert

Screenshots / Recordings (if applicable)

Before

image

After

image image

The ENS names are not displayed here due to my RPC urls setup, but in production (with the production RPC urls) they display just fine.

Summary by CodeRabbit

Release Notes

  • New Features
    • Enhanced vote display now shows voter details and stake information
    • Added responsive vote tables (desktop) and cards (mobile) for voting history
    • Introduced voter voting history popover displaying poll participation details
    • Added tabbed navigation on voting page to switch between overview and votes
    • Improved vote detail cards with transaction hash and timestamp information

Review Change Stack

ibsule added 3 commits May 27, 2026 12:01
…overnance page

- Updated GraphQL queries to include additional fields for votes, such as choiceID, voter, voteStake, and nonVoteStake.
- Introduced a new `parsePollText` function to handle proposal parsing from IPFS.
- Enhanced the PollVotingWidget component to display a link to view votes and improved the layout for better user experience.
- Added a new PollVotingTable component for displaying detailed vote information.
- Updated the voting page to support tab navigation between overview and votes, improving accessibility to voting data.

These changes aim to provide a more comprehensive voting experience and better data handling for polls.
…te components

- Removed duplicate imports and organized import statements for clarity in DesktopVoteTable, index, MobileVoteCards, and MobileVoteView components.
- Ensured consistent formatting and spacing in various files to enhance readability.
- Updated GraphQL queries to maintain structure and improve maintainability.
- Minor adjustments to the PollVotingWidget and PollVotePopover components for better integration.
- Updated the PollVote components to include vote stake information, allowing for better transparency in voting data.
- Modified the `onSelect` function to pass `voteStake` along with the voter's address and ENS name.
- Introduced a `formatVoteStake` function to format and display vote stakes consistently across DesktopVoteTable, MobileVoteCards, and MobileVoteView components.
- Adjusted GraphQL queries and state management to accommodate the new vote stake data, improving overall functionality and user experience.
@ibsule ibsule requested a review from ECWireless as a code owner May 27, 2026 20:29
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 27, 2026

@ibsule is attempting to deploy a commit to the Livepeer Foundation Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

This PR extends voting transparency to the Governance page, bringing vote visibility (who voted, how, and for what stake) to LIP polls. It adds GraphQL queries for vote events, builds responsive vote display components for desktop and mobile, and integrates tab navigation between poll overview and vote details on the voting page.

Changes

Governance Voting Transparency

Layer / File(s) Summary
GraphQL schema and generated Apollo types
queries/poll.graphql, queries/voteEvents.graphql, apollo/subgraph.ts
Poll query is extended to include vote choice and stake fields, and a new voteEvents query is added to fetch vote events sorted by timestamp with transaction and poll metadata. Apollo types are generated for both queries with hooks.
Vote support mapping and poll text parsing
lib/api/types/votes.ts, lib/api/polls.ts
Vote support mapping is extended to map PollChoice.Yes/No to standardized POLL_VOTES entries. New parsePollText utility fetches and parses poll content from IPFS.
Vote display components
components/PollVote/DesktopVoteTable.tsx, components/PollVote/MobileVoteCards.tsx, components/PollVote/MobileVoteView.tsx, components/PollVote/PollVoteDetail.tsx, components/PollVote/PollVotePopover.tsx
DesktopVoteTable renders a responsive table with voter, support, stake, timestamp, and transaction columns. MobileVoteCards wraps paginated MobileVoteView items. PollVoteDetail shows a single vote in responsive mobile/desktop layout with poll title and transaction details. PollVotePopover displays a voter's vote history via modal with vote event list.
Vote orchestration and data fetching
components/PollVote/index.tsx
PollVotingTable orchestrates vote fetching, ENS resolution with caching, vote decoration with transaction/timestamp, and responsive rendering (desktop table or mobile paginated cards with voter selection for popover).
Page navigation and widget integration
pages/voting/[poll].tsx, components/PollVotingWidget/index.tsx
Governance page introduces view-based tab navigation (overview vs. votes) via URL query, builds votesTabHref for linking, and conditionally renders poll overview or vote table. PollVotingWidget adds optional votesTabHref prop and "View votes" link in results section.

Sequence Diagram

sequenceDiagram
  participant User
  participant Page as voting/[poll].tsx
  participant Widget as PollVotingWidget
  participant Table as PollVotingTable
  participant Apollo as Apollo Query
  participant Popover as PollVotePopover
  User->>Page: Load governance poll page
  Page->>Table: Render vote table (view=votes)
  Table->>Apollo: useQuery(poll votes + voteEvents)
  Apollo-->>Table: votes with ENS, tx hash, timestamp
  Table->>Page: Render desktop table or mobile cards
  User->>Table: Click voter row/card
  Table->>Popover: Show voter's vote history
  Popover->>Apollo: useVoteEventsQuery(voter filter)
  Apollo-->>Popover: Sorted vote events
  Popover->>Page: Render PollVoteDetail list
  User->>Page: Click "View votes" link in widget
  Widget->>Page: Navigate to view=votes
Loading

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding voting transparency to the governance page with enhanced polling functionality.
Description check ✅ Passed The PR description is comprehensive, covering purpose, changes made, testing status, risk assessment, and includes before/after screenshots.
Linked Issues check ✅ Passed All code changes align with issue #482: extending voting transparency to governance polls by reusing mechanisms, adding vote detail visibility, and maintaining consistency with Treasury pages.
Out of Scope Changes check ✅ Passed All changes are within scope of the linked issues: vote transparency UI components, graphql queries, data layer updates, and voting page enhancements directly support the feature requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🤖 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 `@components/PollVote/index.tsx`:
- Around line 114-118: The Index component currently ignores the error returned
by useVotes, causing request failures to be rendered as an empty “No votes
found” state; update Index to consume the error value returned from useVotes
(the error/pollError or pollVoteEventsError aggregated into error) and render an
explicit error state (message and optional retry UI) when error is truthy before
the empty-state logic, ensuring you still respect votesLoading/loading to show
spinners while loading and only show the empty-state when there is no error and
no votes.

In `@components/PollVote/MobileVoteView.tsx`:
- Around line 75-77: In MobileVoteView.tsx there are external anchor elements
that set target="_blank" (the one rendering
href={`https://explorer.livepeer.org/accounts/${vote.voter}/delegating`} and the
other at the later occurrence) but do not include rel protection; update both
anchor elements (in the MobileVoteView component) to add rel="noopener
noreferrer" alongside target="_blank" to prevent reverse-tabnabbing and other
security issues.

In `@components/PollVote/PollVoteDetail.tsx`:
- Around line 80-82: Add rel="noopener noreferrer" to any anchor that opens in a
new tab to prevent reverse-tabnabbing; specifically update the anchor rendering
href={`/voting/${vote.poll.id}`} (inside the PollVoteDetail component) and the
other anchor instance around lines ~185-187 to include rel="noopener noreferrer"
alongside target="_blank" so both links use target="_blank" rel="noopener
noreferrer".

In `@components/PollVote/PollVotePopover.tsx`:
- Around line 41-43: PollVotePopover is currently counting votes by checking
choiceID === "0" and "1" which undercounts when events use enum keys like
PollChoice.Yes/PollChoice.No; update the counting to normalize each
voteEvent.choiceID via the existing POLL_VOTES/VOTING_SUPPORT_MAP semantics (or
a small helper normalizeChoiceID) and then compute totals by testing the
normalized support value (e.g., SUPPORT_FOR vs SUPPORT_AGAINST) rather than raw
"0"/"1" strings so voteEvents.map/ filter logic uses the canonical support
mapping for for/against/abstain.
- Around line 60-62: In PollVotePopover (the JSX anchor that renders
href={`https://explorer.livepeer.org/accounts/${voter}/delegating`} and uses
target="_blank"), add the rel="noopener noreferrer" attribute to the anchor
element to prevent reverse-tabnabbing; update the anchor in the PollVotePopover
component where the external account link is created to include rel="noopener
noreferrer".

In `@lib/api/polls.ts`:
- Around line 64-69: parsePollText currently calls catIpfsJson(proposal) and
parsePollIpfs without handling IPFS/network errors, so wrap the fetch and parse
in a try/catch inside parsePollText (the function returning Promise<Fm | null)
and return null on any failure; ensure you call catIpfsJson<IpfsPoll>(proposal)
and then parsePollIpfs(ipfsObject) inside the try, and in the catch optionally
log the error (e.g., via console.error or existing logger) before returning null
to avoid unhandled promise rejections.

In `@pages/voting/`[poll].tsx:
- Around line 314-316: The current implementation hides tab panes with CSS
(using view) but keeps both component trees mounted so voteContent() still
executes; change to conditional JSX rendering so only the active pane mounts by
using a state/derived variable (e.g., safeView or view) to choose between
rendering the Overview component and the Votes component (replace the Box
wrappers that use display toggles with conditional expressions like safeView ===
"overview" ? <Overview .../> : <Votes .../>), and ensure voteContent() is only
called/used inside the Votes branch so expensive work runs only when Votes is
active.
- Line 55: The query-derived view value is not validated: normalize it by
creating a safeView (e.g., const safeView = view === "votes" ? "votes" :
"overview") and use safeView everywhere the component reads tab state or
conditionally renders (replace uses of view in tab state initialization and the
conditional rendering blocks around the overview/votes content, e.g., where view
is referenced at the tab state and the render checks currently at lines ~315 and
~469); this ensures any unsupported query value falls back to "overview" and
prevents empty main content.

In `@queries/voteEvents.graphql`:
- Around line 1-6: The voteEvents query call in
components/PollVote/PollVotePopover.tsx is missing a bounded `first` variable
and can return unbounded results; update the useVoteEventsQuery invocation in
PollVotePopover (the hook call) to pass a safe limit (e.g., `first: 200`)
consistent with components/PollVote/index.tsx, or alternatively set a default in
the GraphQL definition by changing `$first: Int` to `$first: Int = 200` in
queries/voteEvents.graphql so the query is always paginated.
🪄 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: 346f92d3-4693-46b7-a689-465fd40a8b1e

📥 Commits

Reviewing files that changed from the base of the PR and between 9959381 and 9e80ed9.

📒 Files selected for processing (13)
  • apollo/subgraph.ts
  • components/PollVote/DesktopVoteTable.tsx
  • components/PollVote/MobileVoteCards.tsx
  • components/PollVote/MobileVoteView.tsx
  • components/PollVote/PollVoteDetail.tsx
  • components/PollVote/PollVotePopover.tsx
  • components/PollVote/index.tsx
  • components/PollVotingWidget/index.tsx
  • lib/api/polls.ts
  • lib/api/types/votes.ts
  • pages/voting/[poll].tsx
  • queries/poll.graphql
  • queries/voteEvents.graphql

Comment on lines +114 to +118
return {
votes,
loading: loading || votesLoading,
error: pollError || pollVoteEventsError,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Query errors are dropped and rendered as empty results

  • Problem: useVotes exposes error, but Index ignores it (Line 133) and can render “No votes found” on request failures.
  • Why it matters: Users see incorrect state, and operational issues are hidden.
  • Suggested fix: Consume error in Index and render a dedicated error state/message (or retry UI) before empty-state logic.

Also applies to: 133-177

🤖 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 `@components/PollVote/index.tsx` around lines 114 - 118, The Index component
currently ignores the error returned by useVotes, causing request failures to be
rendered as an empty “No votes found” state; update Index to consume the error
value returned from useVotes (the error/pollError or pollVoteEventsError
aggregated into error) and render an explicit error state (message and optional
retry UI) when error is truthy before the empty-state logic, ensuring you still
respect votesLoading/loading to show spinners while loading and only show the
empty-state when there is no error and no votes.

Comment on lines +75 to +77
href={`https://explorer.livepeer.org/accounts/${vote.voter}/delegating`}
target="_blank"
css={{
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

target="_blank" links missing rel protection

  • Problem: External links opened in a new tab (Line 76, Line 146) omit rel="noopener noreferrer".
  • Why it matters: This allows reverse-tabnabbing and weakens security posture.
  • Suggested fix: Add rel="noopener noreferrer" to both links.

Also applies to: 145-147

🤖 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 `@components/PollVote/MobileVoteView.tsx` around lines 75 - 77, In
MobileVoteView.tsx there are external anchor elements that set target="_blank"
(the one rendering
href={`https://explorer.livepeer.org/accounts/${vote.voter}/delegating`} and the
other at the later occurrence) but do not include rel protection; update both
anchor elements (in the MobileVoteView component) to add rel="noopener
noreferrer" alongside target="_blank" to prevent reverse-tabnabbing and other
security issues.

Comment on lines +80 to +82
href={`/voting/${vote.poll.id}`}
target="_blank"
css={{
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

New-tab links need rel="noopener noreferrer"

  • Problem: Links with target="_blank" (Line 81, Line 186) do not set rel.
  • Why it matters: Creates reverse-tabnabbing exposure.
  • Suggested fix: Add rel="noopener noreferrer" on both link instances.

Also applies to: 185-187

🤖 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 `@components/PollVote/PollVoteDetail.tsx` around lines 80 - 82, Add
rel="noopener noreferrer" to any anchor that opens in a new tab to prevent
reverse-tabnabbing; specifically update the anchor rendering
href={`/voting/${vote.poll.id}`} (inside the PollVoteDetail component) and the
other anchor instance around lines ~185-187 to include rel="noopener noreferrer"
alongside target="_blank" so both links use target="_blank" rel="noopener
noreferrer".

Comment on lines +41 to +43
for: voteEvents.filter((v) => v.choiceID === "0").length,
against: voteEvents.filter((v) => v.choiceID === "1").length,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Vote summary can undercount due to hardcoded choice keys

  • Problem: Stats only count choiceID === "0" and "1" (Line 41-42).
  • Why it matters: If vote events come through PollChoice.Yes/No, totals for “For/Against” show incorrect values.
  • Suggested fix: Normalize with POLL_VOTES/VOTING_SUPPORT_MAP semantics (handle both "0"/"1" and Yes/No) before counting.
🤖 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 `@components/PollVote/PollVotePopover.tsx` around lines 41 - 43,
PollVotePopover is currently counting votes by checking choiceID === "0" and "1"
which undercounts when events use enum keys like PollChoice.Yes/PollChoice.No;
update the counting to normalize each voteEvent.choiceID via the existing
POLL_VOTES/VOTING_SUPPORT_MAP semantics (or a small helper normalizeChoiceID)
and then compute totals by testing the normalized support value (e.g.,
SUPPORT_FOR vs SUPPORT_AGAINST) rather than raw "0"/"1" strings so
voteEvents.map/ filter logic uses the canonical support mapping for
for/against/abstain.

Comment on lines +60 to +62
href={`https://explorer.livepeer.org/accounts/${voter}/delegating`}
target="_blank"
css={{
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing rel on external new-tab account link

  • Problem: Link opens with target="_blank" (Line 61) without rel="noopener noreferrer".
  • Why it matters: Reverse-tabnabbing risk.
  • Suggested fix: Add rel="noopener noreferrer".
🤖 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 `@components/PollVote/PollVotePopover.tsx` around lines 60 - 62, In
PollVotePopover (the JSX anchor that renders
href={`https://explorer.livepeer.org/accounts/${voter}/delegating`} and uses
target="_blank"), add the rel="noopener noreferrer" attribute to the anchor
element to prevent reverse-tabnabbing; update the anchor in the PollVotePopover
component where the external account link is created to include rel="noopener
noreferrer".

Comment thread lib/api/polls.ts
Comment on lines +64 to +69
export const parsePollText = async (proposal: string): Promise<Fm | null> => {
const ipfsObject = await catIpfsJson<IpfsPoll>(proposal);
const attributes = parsePollIpfs(ipfsObject);

return attributes;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unhandled IPFS fetch failures in parsePollText

  • Problem: catIpfsJson errors are not caught in parsePollText (Line 65), so failed IPFS reads reject the promise.
  • Why it matters: Consumers can hit unhandled async failures and lose vote-title rendering, especially on intermittent IPFS/network issues.
  • Suggested fix: Wrap the fetch/parse in try/catch and return null on failure.
Proposed fix
 export const parsePollText = async (proposal: string): Promise<Fm | null> => {
-  const ipfsObject = await catIpfsJson<IpfsPoll>(proposal);
-  const attributes = parsePollIpfs(ipfsObject);
-
-  return attributes;
+  try {
+    const ipfsObject = await catIpfsJson<IpfsPoll>(proposal);
+    return parsePollIpfs(ipfsObject);
+  } catch {
+    return null;
+  }
 };
📝 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.

Suggested change
export const parsePollText = async (proposal: string): Promise<Fm | null> => {
const ipfsObject = await catIpfsJson<IpfsPoll>(proposal);
const attributes = parsePollIpfs(ipfsObject);
return attributes;
};
export const parsePollText = async (proposal: string): Promise<Fm | null> => {
try {
const ipfsObject = await catIpfsJson<IpfsPoll>(proposal);
return parsePollIpfs(ipfsObject);
} catch {
return null;
}
};
🤖 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 64 - 69, parsePollText currently calls
catIpfsJson(proposal) and parsePollIpfs without handling IPFS/network errors, so
wrap the fetch and parse in a try/catch inside parsePollText (the function
returning Promise<Fm | null) and return null on any failure; ensure you call
catIpfsJson<IpfsPoll>(proposal) and then parsePollIpfs(ipfsObject) inside the
try, and in the catch optionally log the error (e.g., via console.error or
existing logger) before returning null to avoid unhandled promise rejections.

Comment thread pages/voting/[poll].tsx

const [pollData, setPollData] = useState<PollExtended | null>(null);
const { query } = router;
const view = query?.view?.toString().toLowerCase() || "overview";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize view to supported values before rendering.

  • Problem: Line 55 accepts any view query value, but Line 315 and Line 469 only render content for "overview" or "votes".
  • Why it matters: Invalid/stale URLs can render an empty main content area.
  • Suggested fix: Derive a safe value, e.g. const safeView = view === "votes" ? "votes" : "overview";, and use safeView for tab state and conditional rendering.

Also applies to: 315-316, 469-470

🤖 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 at line 55, The query-derived view value is not
validated: normalize it by creating a safeView (e.g., const safeView = view ===
"votes" ? "votes" : "overview") and use safeView everywhere the component reads
tab state or conditionally renders (replace uses of view in tab state
initialization and the conditional rendering blocks around the overview/votes
content, e.g., where view is referenced at the tab state and the render checks
currently at lines ~315 and ~469); this ensures any unsupported query value
falls back to "overview" and prevents empty main content.

Comment thread pages/voting/[poll].tsx
Comment on lines +314 to +316
<Box css={{ marginTop: "$4" }}>
<Box css={{ display: view === "overview" ? "block" : "none" }}>
<Box
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid CSS-only tab hiding for expensive content.

  • Problem: Line 314–316 and Line 469–470 hide panes with display, but still mount both trees; voteContent() at Line 470 runs regardless of active tab.
  • Why it matters: The votes table path can do unnecessary render/data work when users stay on Overview.
  • Suggested fix: Render conditionally in JSX (safeView === "overview" ? <Overview/> : <Votes/>) so only the active pane mounts.

Also applies to: 469-470

🤖 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 314 - 316, The current implementation
hides tab panes with CSS (using view) but keeps both component trees mounted so
voteContent() still executes; change to conditional JSX rendering so only the
active pane mounts by using a state/derived variable (e.g., safeView or view) to
choose between rendering the Overview component and the Votes component (replace
the Box wrappers that use display toggles with conditional expressions like
safeView === "overview" ? <Overview .../> : <Votes .../>), and ensure
voteContent() is only called/used inside the Votes branch so expensive work runs
only when Votes is active.

Comment on lines +1 to +6
query voteEvents($first: Int, $where: VoteEvent_filter) {
voteEvents(
orderBy: timestamp
orderDirection: desc
first: $first
where: $where
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that all usages of the voteEvents query provide a 'first' limit

rg -nP --type=ts --type=tsx -A5 'useVoteEventsQuery|voteEvents' -g '!queries/' -g '!apollo/'

Repository: livepeer/explorer

Length of output: 89


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Find all JS/TS/TSX call sites that mention voteEvents usage/hooks.
rg -n --glob '*.{ts,tsx,js,jsx}' -S 'voteEvents' .

# 2) Narrow: find GraphQL variable usage patterns for `first` alongside the voteEvents query.
rg -n --glob '*.{ts,tsx,js,jsx}' -S '(first\s*:|first\s*=|variables\s*:\s*\{[^}]*first|first\s*\?)' .

# 3) Specifically look for a hook named like useVoteEventsQuery and inspect nearby code.
rg -n --glob '*.{ts,tsx}' -S 'useVoteEventsQuery' . -A30 -B10

# 4) If the query is imported as voteEvents.gql/graphQL, locate the import and inspect usage.
rg -n --glob '*.{ts,tsx,js,jsx}' -S 'from\s+[\'"].*voteEvents\.graphql[\'"]|voteEvents\.graphql' . -A20 -B5

Repository: livepeer/explorer

Length of output: 20603


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List all call sites of useVoteEventsQuery and inspect variables for `first`
rg -n --glob '*.{ts,tsx}' 'useVoteEventsQuery\s*\(' . -S | cut -d: -f1-2 | sort -u | while read -r file; do
  echo "==== $file ===="
  rg -n --glob "$file" 'useVoteEventsQuery\s*\(' . -S -A25 -B5
done

Repository: livepeer/explorer

Length of output: 98


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show voteEvents usages around useVoteEventsQuery(
rg -n --glob 'components/**/*.{ts,tsx,js,jsx}' -S 'useVoteEventsQuery\s*\(' components/PollVote -A25 -B5 || true

# Inspect the full variables object for PollVotePopover usage
sed -n '1,120p' components/PollVote/PollVotePopover.tsx

# Inspect generated hook defaults in apollo/subgraph.ts around useVoteEventsQuery
rg -n 'export function useVoteEventsQuery' apollo/subgraph.ts
sed -n '11180,11230p' apollo/subgraph.ts

Repository: livepeer/explorer

Length of output: 9372


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n --glob '*.{ts,tsx,js,jsx}' 'useVoteEventsQuery\s*\(\s*\{' . -S

Repository: livepeer/explorer

Length of output: 327


Add pagination (first) to the voteEvents query in PollVotePopover

  • Problem: queries/voteEvents.graphql leaves $first optional, and components/PollVote/PollVotePopover.tsx calls useVoteEventsQuery without providing first (while components/PollVote/index.tsx correctly sets first: 200).
  • Why it matters: voters with large vote histories can trigger large/unbounded responses, increasing latency and risking timeouts/perf issues.
  • Suggested fix: pass a bounded first in components/PollVote/PollVotePopover.tsx (e.g., first: 200, matching the other usage) or add a safe default for $first in queries/voteEvents.graphql (e.g., $first: Int = 200).
🤖 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 `@queries/voteEvents.graphql` around lines 1 - 6, The voteEvents query call in
components/PollVote/PollVotePopover.tsx is missing a bounded `first` variable
and can return unbounded results; update the useVoteEventsQuery invocation in
PollVotePopover (the hook call) to pass a safe limit (e.g., `first: 200`)
consistent with components/PollVote/index.tsx, or alternatively set a default in
the GraphQL definition by changing `$first: Int` to `$first: Int = 200` in
queries/voteEvents.graphql so the query is always paginated.

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.

Extend Voting Transparency to Governance page

1 participant