diff --git a/src/app/governance/compare/page.tsx b/src/app/governance/compare/page.tsx
new file mode 100644
index 0000000..639973b
--- /dev/null
+++ b/src/app/governance/compare/page.tsx
@@ -0,0 +1,113 @@
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { getGovernanceProposalById, getGovernanceProposals } from "@/lib/governanceStore";
+import type { GovernanceProposal } from "@/lib/governanceStore";
+import { ProposalStatusChip } from "@/components/ProposalStatusChip";
+import { BondImpact } from "../[id]/ProposalImpactPreview";
+import { sanitizeText } from "@/lib/textSafety";
+
+interface ComparePageProps {
+ searchParams?: { a?: string; b?: string };
+}
+
+function ProposalColumn({ proposal, showImpact }: { proposal: GovernanceProposal; showImpact: boolean }) {
+ const change = proposal.parameterChange;
+ return (
+
+
+ {sanitizeText(proposal.title)}
+
+ - Category
+ - {proposal.category}
+ - Votes for
+ - {proposal.votesFor.toLocaleString()}
+ - Votes against
+ - {proposal.votesAgainst.toLocaleString()}
+ - Votes abstain
+ - {(proposal.votesAbstain ?? 0).toLocaleString()}
+ - Parameter change
+ -
+ {change ? `${change.parameter}: ${change.currentValue} → ${change.proposedValue}` : "None"}
+
+
+ {showImpact && change && (
+
+
Projected solver impact
+
+
+ )}
+
+ );
+}
+
+export default function GovernanceComparePage({ searchParams }: ComparePageProps) {
+ const aId = searchParams?.a;
+ const bId = searchParams?.b;
+
+ const a = aId ? getGovernanceProposalById(aId) : undefined;
+ if (aId && !a) notFound();
+ const b = bId ? getGovernanceProposalById(bId) : undefined;
+ if (bId && !b) notFound();
+
+ if (!a || !b) {
+ const proposals = getGovernanceProposals();
+ return (
+
+ Compare proposals
+
+
+ );
+ }
+
+ const sameParam =
+ a.id !== b.id &&
+ a.parameterChange?.parameter === "minSolverBondUsd" &&
+ b.parameterChange?.parameter === "minSolverBondUsd";
+
+ return (
+
+
+ ← Back to Proposals
+
+
+ Compare {a.id} and {b.id}
+
+ {a.id === b.id && (
+
+ Both selections are the same proposal.
+
+ )}
+
+ {!sameParam && (
+
+ An impact comparison is not available: the proposals do not share a comparable parameter change, so only
+ tallies and metadata are shown.
+
+ )}
+
+ );
+}
diff --git a/src/app/governance/delegates/page.tsx b/src/app/governance/delegates/page.tsx
index 76a1db7..4316389 100644
--- a/src/app/governance/delegates/page.tsx
+++ b/src/app/governance/delegates/page.tsx
@@ -1,51 +1,17 @@
'use client';
+import Link from 'next/link';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useVotingPower } from '@/hooks/useVotingPower';
import { useDelegation } from '@/hooks/useDelegation';
import { sanitizeText } from '@/lib/textSafety';
import { isSameStellarAddress, isValidStellarAddress } from '@/lib/stellarAddress';
+import { MOCK_DELEGATES, type DelegateProfile } from '@/lib/delegateDirectory';
import { secureLogger } from '@/lib/secureLogging';
-interface DelegateProfile {
- address: string;
- name: string;
- bio: string;
- votingPower: number;
- participationRate: number;
- proposalsVoted: number;
-}
-
type SortKey = 'votingPower' | 'participationRate' | 'proposalsVoted';
type FlowStage = 'idle' | 'review' | 'simulate' | 'sign' | 'pending' | 'done';
-const MOCK_DELEGATES: DelegateProfile[] = [
- {
- address: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF',
- name: 'Aurora Governance',
- bio: 'Active voter focused on protocol upgrades and treasury stewardship.',
- votingPower: 1250000,
- participationRate: 0.94,
- proposalsVoted: 87,
- },
- {
- address: 'GBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBWHF',
- name: 'Nova Collective',
- bio: 'Community-run delegate prioritising developer tooling and grants.',
- votingPower: 860000,
- participationRate: 0.81,
- proposalsVoted: 64,
- },
- {
- address: 'GCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCWHF',
- name: 'Meridian Labs',
- bio: 'Security-first delegate reviewing every proposal before voting.',
- votingPower: 540000,
- participationRate: 0.72,
- proposalsVoted: 51,
- },
-];
-
function formatPower(value: number): string {
return new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value);
}
@@ -96,6 +62,13 @@ export default function DelegatesPage() {
setConfirmedAddress('');
}, []);
+ // Deep link from the voter-history page: /governance/delegates?delegate=G...
+ useEffect(() => {
+ const addr = new URLSearchParams(window.location.search).get('delegate');
+ const match = MOCK_DELEGATES.find((d) => isSameStellarAddress(d.address, addr ?? ''));
+ if (match) setSelected(match);
+ }, []);
+
useEffect(() => {
if (stage === 'done') {
void refreshVotingPower();
@@ -234,6 +207,13 @@ export default function DelegatesPage() {
{sanitizeText(d.name)}
{d.address}
+