Conversation
- Introduced a new component to present a list of orchestrators with their voting statistics, including proposals voted on, votes casted, recent votes, and voting turnout.
- Added Cube.js client for data fetching with new utility functions for querying voting history. - Introduced VotingHistoryView component to display voting statistics, including proposals voted on and voting turnout. - Updated package.json and pnpm-lock.yaml to include Cube.js client dependencies.
- Exported VoterSummary type for better type management in voting components. - Integrated VotingHistoryView into the account layout for displaying voting statistics. - Added new VotingHistory page to handle routing and data fetching for user voting history. - Updated account layout to include a new tab for voting history, improving user navigation. - Implemented utility functions for processing and summarizing voting data from Cube.js.
- Added OrchestratorVotingList component to display voting data for orchestrators. - Implemented tabbed interface for Yield Overview and Voting History sections. - Integrated voter summary processing functions to aggregate voting data. - Updated getStaticProps to fetch and pass initial voter data to the page.
- Adds treasury proposals voting overview
- Changed the method of accessing raw data from Cube.js API to use a function call. - Simplified response validation in getStaticProps by removing unnecessary checks and directly using the response for voter summaries.
- Fetches voting metrics (voted/eligible) from Cube.js using LivepeerVoteProposals - Adds an interactive Stat card to OrchestratingView with a visual progress bar - Displays participation rate percentage and integrated link to account history - Adds utility to the Cube query generator
…nology - Moved to a friendlier "Sky & Tomato" color scheme to keep voting separate from our main green brand accent. - Standardized labels to "For / Against / Abstain" everywhere, including the global transaction feed. - Rebuilt the vote table using our core data table component for a consistent look. - Made it easier to explore voter history with new dedicated buttons, better tooltips, and subtle animations. - Cleaned up the layout by stripping out redundant titles and shrinking fonts where they were getting too loud. - Polished the details: added specific styling for "No reason provided" and refined how voter profiles are we showing voter names and txn links.
- Eliminated the agent log useEffect that was posting debug information. - Streamlined imports by removing unnecessary useEffect import.
…List - Changed badge colors to use a "Sky & Tomato" color scheme for better visual distinction. - Updated badge labels from "Yes/No" to "For/Against" to standardize terminology across the application.
… horizontal scrolling
- Enhance typography hierarchy and spacing in the 'Your vote' section. - Implement translucent button styles with high-contrast labels and subtle borders. - Improve progress bar visibility and color contrast for better scannability. - Adjust voting reason input for improved visual balance and focus states. Fixes #464
- Eliminated the 'Voting History' tab from the account layout. - Removed related imports and conditional rendering for improved code clarity.
- Introduced GraphQL queries for fetching treasury vote events and vote events. - Implemented corresponding React hooks for easy integration in components. - Enhanced the data structure to include relevant fields such as proposal details and voter information.
- Added functionality to fetch and display treasury vote events and vote events in the HistoryView component. - Enhanced data handling with hooks to manage and extend event data, including proposal details and attributes. - Updated rendering logic to accommodate new event types and improve user experience with detailed information on votes.
…into the history table
- Deleted the Cube.js client and associated query generator as their functionality has been replaced by direct GraphQL queries. - Updated components to utilize new treasury proposal and vote queries, enhancing data fetching and management. - Refactored the OrchestratingView component to integrate treasury votes and proposals directly through Apollo hooks.
- Updated the TreasuryVotesQuery to include the voter ID for better data management. - Integrated ENS name fetching for voters in the VoteTable component, improving user experience. - Refactored vote rendering logic in DesktopVoteTable and MobileVoteTable to utilize the new voting support mapping. - Enhanced error handling in VoteTable to display error messages more clearly. - Improved overall data structure and state management for votes and events.
… component in HistoryView
…rast - Add mobile card layout matching MobileVoteView, use TransactionBadge component - Hide timeline on mobile, improve contrast with $loContrast modal background - Maintain desktop timeline layout
- Display formatted timestamp (MM/DD/YYYY h:mm a) below proposal ID - Show on both mobile and desktop layouts
- Move transaction timestamp to bottom row grouped with transaction badge - Narrow VoteModal desktop width from 50% to 40% with 600px max-width - Improve reason text readability with shorter line lengths - Better visual hierarchy by grouping transaction metadata together
- Redesign VoteDetail and MobileVoteView cards with improved visual hierarchy - Make vote badge the hero element (top-left, larger size with icon) - Remove redundant visual signals (left border, truncated proposal ID) - Implement collapsible reason display (collapsed by default) for cleaner layout - Simplify footer to single-line transaction badge and timestamp - Add transaction timestamp to VoteItem for better context - Refactor spacing and layout for consistency across mobile and desktop views - Align card styling with Livepeer design system patterns
* refactor: improve mobile vote item hover behaviour Ensure the hover behaviour is in the voting item is consistent with the hover behavoir used in the rest of the explorer. * refactor: reuse DataTable pagination for mobile vote tabke Reuse the existing arrow-only pagination from DataTable in the mobile voting table to ensure consistent behavior and styling.
- Add gap between badge and title on mobile - Align badge left edge with title text
* refactor: simplify transactionlist treasury parsing Updates the events query so that we can use it directly for parsing the treasury voting events. * chore: fix history view type errors Ensure types in the history view correctly account for the new proposal event.
- Introduced a new VoteHistoryModal component to enhance user experience by allowing users to view their voting history in a modal format. - Updated VotePopover to utilize the new VoteHistoryModal instead of the previous VoteModal.
Remove debug query which was accidentally added in #529.
Ensures that the linking behavoir for the other events is similar to the voting behavoir. Also create an extra ipfs helper so code is cleaner.
Improve user experience by using appolos caching feature.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR refactors the votes caching behavior in the Treasury Vote Table component to improve performance by avoiding redundant ENS lookups. The changes introduce a module-level cache for ENS names and modify Apollo's fetch policy to better balance cache usage with data freshness.
Changes:
- Added module-level
ensCacheto persist ENS name lookups across component renders and navigations - Changed Apollo query fetch policies from default to "cache-and-network" for both votes and vote events queries
- Updated ENS lookup logic to check and populate the module-level cache
Comments suppressed due to low confidence (2)
components/Treasury/TreasuryVoteTable/index.tsx:86
- Inconsistent cache behavior when ENS lookup fails. If getEnsForVotes throws an exception (caught at line 83), the address is never added to ensCache. This means failed lookups will be retried on every render, potentially causing repeated failed network requests for addresses that consistently fail to resolve.
Consider caching the formatted address even when the lookup fails, to prevent repeated attempts for the same failing address.
} catch (e) {
console.warn(`Failed to fetch ENS for ${address}`, e);
}
})
components/Treasury/TreasuryVoteTable/index.tsx:108
- Logic error: the condition at lines 51-56 clears votes when data is missing, but doesn't prevent decorateVotes from executing afterwards. This means decorateVotes() at line 108 will still run with undefined data, causing uniqueVoters to be an empty array and votes to be set to an empty array anyway. The early setVotes([]) at line 55 is redundant and the condition should use an early return to prevent unnecessary processing.
if (
!treasuryVotesData?.treasuryVotes ||
!treasuryVoteEventsData?.treasuryVoteEvents
) {
setVotes([]);
}
const decorateVotes = async () => {
setVotesLoading(true);
const uniqueVoters = Array.from(
new Set(treasuryVotesData?.treasuryVotes?.map((v) => v.voter.id) ?? [])
);
const localEnsCache: { [address: string]: string } = {};
await Promise.all(
uniqueVoters.map(async (address) => {
try {
if (localEnsCache[address]) {
return;
}
if (ensCache[address]) {
localEnsCache[address] = ensCache[address];
return;
}
const ensAddress = await getEnsForVotes(address);
if (ensAddress && ensAddress.name) {
localEnsCache[address] = ensAddress.name;
} else {
localEnsCache[address] = formatAddress(address);
}
ensCache[address] = localEnsCache[address];
} catch (e) {
console.warn(`Failed to fetch ENS for ${address}`, e);
}
})
);
const votes =
treasuryVotesData?.treasuryVotes?.map((vote) => {
const events = (treasuryVoteEventsData?.treasuryVoteEvents ?? [])
.filter((event) => event.voter.id === vote.voter.id)
.sort((a, b) => b.timestamp - a.timestamp);
const latestEvent = events[0];
const ensName = localEnsCache[vote.voter.id] ?? "";
return {
...vote,
reason: latestEvent?.reason || vote.reason || "",
ensName,
transactionHash: latestEvent?.transaction.id ?? "",
timestamp: latestEvent?.timestamp,
};
}) ?? [];
setVotes(votes as Vote[]);
setVotesLoading(false);
};
decorateVotes();
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (ensCache[address]) { | ||
| localEnsCache[address] = ensCache[address]; | ||
| return; | ||
| } | ||
|
|
||
| const ensAddress = await getEnsForVotes(address); | ||
|
|
||
| if (ensAddress && ensAddress.name) { | ||
| localEnsCache[address] = ensAddress.name; | ||
| } else { | ||
| localEnsCache[address] = formatAddress(address); | ||
| } | ||
| ensCache[address] = localEnsCache[address]; |
There was a problem hiding this comment.
Race condition vulnerability in concurrent ENS lookups. When multiple parallel lookups occur for the same address (via Promise.all), they all check ensCache at the same time (line 70), all miss, and then all make the same getEnsForVotes call (line 75). Each then updates ensCache (line 82), causing redundant network requests. This defeats the purpose of the cache.
Consider tracking in-flight requests to prevent duplicate lookups. A common pattern is to store the Promise itself in the cache while the request is pending, so subsequent checks can await the same Promise rather than initiating new requests.
| const ensCache: Record<string, string> = {}; | ||
|
|
There was a problem hiding this comment.
Potential memory leak: the module-level ensCache grows indefinitely without any expiration or size limit. Over time, as users navigate through multiple proposals with different voters, this cache will accumulate entries for all voters ever seen, consuming increasing amounts of memory for the lifetime of the browser session.
Consider implementing a cache size limit (e.g., LRU eviction) or time-based expiration for ENS entries. Alternatively, since ENS names rarely change, document this as an intentional design decision if unbounded growth is acceptable for this use case.
| const ensCache: Record<string, string> = {}; | |
| // Bounded to avoid unbounded memory growth over long-lived sessions. | |
| const ENS_CACHE_MAX_ENTRIES = 500; | |
| const ensCache: Record<string, string> = new Proxy<Record<string, string>>( | |
| {} as Record<string, string>, | |
| { | |
| set(target, prop: string | symbol, value: string): boolean { | |
| // Only enforce limits for string keys; symbols are passed through. | |
| if (typeof prop === "string") { | |
| if (!Object.prototype.hasOwnProperty.call(target, prop)) { | |
| const keys = Object.keys(target); | |
| if (keys.length >= ENS_CACHE_MAX_ENTRIES) { | |
| // Simple FIFO eviction: remove the oldest inserted key. | |
| delete target[keys[0]]; | |
| } | |
| } | |
| } | |
| // Perform the actual write. | |
| // Type assertion because Proxy handler uses loose typing on 'prop'. | |
| (target as any)[prop] = value; | |
| return true; | |
| }, | |
| } | |
| ); |
|
Forwarding the comment at #457 (comment) to potentially move this to a global hook |
@ECWireless, this is just a quick pull request to showcase what I mean. Please feel free to use whatever caching behavior you think makes the most sense. It could be even better to upgrade Apollo and make use of persistent cache for proposals that are already finished. Maybe create a follow up issue for this.