feat(governance,analytics): implement #469 #470 #471 #468 — API layer… - #565
Merged
james2177 merged 2 commits intoSep 30, 2026
Conversation
…llar-vortex-protocol#470 stellar-vortex-protocol#471 stellar-vortex-protocol#468 — API layer, on-chain voting, voting power, market-share analytics ## Issue stellar-vortex-protocol#469 — Replace in-memory governance mock with API-backed data layer ### Problem src/lib/governanceStore.ts held hard-coded proposals and comments in module-level mutable state, meaning comments vanished on reload and were shared across all server renders. Components called the store synchronously which could not represent loading or error states. ### What was done - Created src/lib/governanceApi.ts with a typed GovernanceApi interface (listProposals, getProposal, listComments, postComment). - Implemented HttpGovernanceApi: fetches from relay endpoints (GET /api/governance/proposals, GET /api/governance/proposals/:id, GET /api/governance/proposals/:id/comments, POST /api/governance/proposals/:id/comments). Validates all responses with runtime type guards; throws GovernanceValidationError on shape mismatches. - Implemented MockGovernanceApi: uses the same deterministic seed data as the old store but with per-instance isolated comment stores — no shared mutable module state between tests. - Adapter factory (createGovernanceApi) selects the implementation from NEXT_PUBLIC_GOVERNANCE_SOURCE ('mock'|'http'); production defaults to 'http' and throws at construction time if NEXT_PUBLIC_API_URL is unset. - Created src/lib/governanceApiContext.tsx: React context + provider so tests can inject fake adapters without monkey-patching modules. - Created src/hooks/useGovernance.ts with SWR hooks: useProposals(filters), useProposal(id), useProposalComments(id). useProposalComments includes optimistic comment posting with rollback via SWR mutate + rollbackOnError. - Migrated GovernancePageClient.tsx: removed direct governanceStore imports, replaced with useProposals(); added loading skeleton, error state, and empty state. - Added src/app/governance/loading.tsx: Next.js loading UI with animated skeletons while proposals fetch. - Added src/app/governance/error.tsx: Next.js error boundary with a 'Try again' reset button. - Added sanitizeText() and validateCommentText() to src/lib/textSafety.ts (previously missing despite being imported). ## Issue stellar-vortex-protocol#470 — On-chain voting: cast For/Against/Abstain with flow states ### Problem The governance page listed proposals but users could not vote. Governance without participation is just a dashboard. ### What was done - Created src/hooks/useVote.ts implementing the full voting flow state machine: idle → building → reviewing → awaiting-signature → submitting → confirmed, with an 'error' escape state at each step. - Guards enforced before flow starts: wallet disconnected, wrong network (checked against NEXT_PUBLIC_NETWORK), zero voting power, deadline passed (with 30-second server-time tolerance), no choice selected. - Optimistic tally delta applied immediately after the user confirms, flagged as 'pending: true' until the transaction is confirmed. On failure the delta is rolled back. - Double-vote handling: after confirmation confirmedChoice is set and the SWR cache for the proposal is invalidated to fetch authoritative tallies. - VoteChoice typed as 'for' | 'against' | 'abstain'; VoteErrorKind discriminates: wallet-disconnected, wrong-network, zero-power, deadline-passed, already-voted, user-rejected, network, generic. - classifyVoteError maps raw Error messages to VoteErrorKind. - Created src/components/VotePanel.tsx: radio group (For/Against/Abstain with ▲/▼ accessible labels), tally progress bar with optimistic overlay, finality copy ('Votes are final'), multi-step status label, submit button with aria-busy, error dismiss control. - VotePanel integrated into ProposalDetailClient: shown only for active proposals, gated behind wallet connect for inactive wallet state. ## Issue stellar-vortex-protocol#471 — Voting-power hydration with snapshot semantics ### Problem Users needed to know before voting whether they could vote and with what weight. Snapshot semantics (power frozen at proposal creation) are a common source of confusion. ### What was done - Created src/hooks/useVotingPower.ts: useVotingPower(address, proposalId) → { power, isLoading, error, canVote } Power shape: { own, delegatedIn, delegatedOut, total, snapshotAt }. SWR cache keyed by [address, proposalId]; a wallet address change automatically invalidates the cache (different key). Adapter duck-typed: if the GovernanceApi instance exposes getVotingPower it is called; otherwise a graceful zero-power fallback is returned so the page renders without blocking. - formatVotingPower(amount) helper: uses compact notation for values ≥ 1,000 (e.g. '125K') with the full precise value on title/aria-label for hover/focus (WCAG accessible). - Created src/components/VotingPowerPanel.tsx: shows total weight, own / delegatedIn / delegatedOut breakdown (hidden when all zero), snapshot date tooltip explaining semantics, and a 0-power guidance block explaining how to acquire tokens or request delegation. - VotingPowerPanel integrated into ProposalDetailClient sidebar, visible when wallet is connected. ## Issue stellar-vortex-protocol#468 — Market-share and top-movers analytics ### Problem Current analytics only showed point-in-time snapshots. Product and community teams needed to see shifts (which chain is growing, whether one solver is becoming dominant) for governance and growth decisions. ### What was done - Added computeShareSeries(intents, dimension) to src/lib/analytics.ts: Buckets intents by day; identifies top-N (5) entities by total volume; collapses remainder into 'Other' (deterministic). For each day computes per-entity share as a % of that day's total volume (zero-volume days produce 0 share — never NaN). Returns { entities, series, colors }. - Added computeConcentration(intents, dimension) to analytics.ts: Computes Herfindahl–Hirschman Index (HHI = Σ share_i²) scaled 0–10,000. Returns hhi, top1SharePct, top3SharePct, level (competitive/moderate/ concentrated), and ranked entity list. - Added computeTopMovers(intents, dimension, windowDays=7): compares share in the last N days vs the preceding N-day window; returns entities ranked by absolute delta with ▲/▼ accessible labels. - Chain colours sourced from CHAINS registry; solver/token colours use an 8-colour accessible fallback palette; 'Other' always maps to gray. - Added StackedAreaChart to AnalyticsPageClient: SVG stacked area with 100%-normalised toggle, per-entity fill opacity, grid lines, and legend. - Added TopMoversPanel: entity, current share %, delta in percentage points with ▲/▼ accessible aria-labels. - Added ConcentrationIndicator: HHI badge in a Tooltip explaining the index, top-1/top-3 share cards, top-5 entity mini-list. - Added dimension selector (Source Chain / Dest Token / Solver) and normalise toggle in AnalyticsPageClient controlling all three new panels. Closes stellar-vortex-protocol#469 Closes stellar-vortex-protocol#470 Closes stellar-vortex-protocol#471 Closes stellar-vortex-protocol#468
|
@allhandghost Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
…1-governance-analytics # Conflicts: # src/app/analytics/AnalyticsPageClient.tsx # src/app/governance/GovernancePageClient.tsx # src/app/governance/[id]/ProposalDetailClient.tsx # src/lib/textSafety.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…, on-chain voting, voting power, market-share analytics
Issue #469 — Replace in-memory governance mock with API-backed data layer
Problem
src/lib/governanceStore.ts held hard-coded proposals and comments in module-level mutable state, meaning comments vanished on reload and were shared across all server renders. Components called the store synchronously which could not represent loading or error states.
What was done
Issue #470 — On-chain voting: cast For/Against/Abstain with flow states
Problem
The governance page listed proposals but users could not vote. Governance without participation is just a dashboard.
What was done
Issue #471 — Voting-power hydration with snapshot semantics
Problem
Users needed to know before voting whether they could vote and with what weight. Snapshot semantics (power frozen at proposal creation) are a common source of confusion.
What was done
Issue #468 — Market-share and top-movers analytics
Problem
Current analytics only showed point-in-time snapshots. Product and community teams needed to see shifts (which chain is growing, whether one solver is becoming dominant) for governance and growth decisions.
What was done
Closes #469
Closes #470
Closes #471
Closes #468
Summary
Closes #
Changes
Testing & QA
npm run check:editorconfigpassesnpm run typecheck(tsc --noEmit) passes cleanlynpm testandnpm run test:coveragepassnpm run buildpassesPreview