From 28efc387bff7c195f01224a879b54a5cc10bae27 Mon Sep 17 00:00:00 2001 From: 0xMosas Date: Fri, 10 Apr 2026 22:44:38 +0100 Subject: [PATCH 01/23] fix(cache): clear min stake entries during invalidate-all Include minStakeAmounts in full cache invalidation so size accounting and invalidateAll behavior remain consistent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- frontend/src/lib/blockchain-cache.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/lib/blockchain-cache.ts b/frontend/src/lib/blockchain-cache.ts index 74c3dadb..ad664f85 100644 --- a/frontend/src/lib/blockchain-cache.ts +++ b/frontend/src/lib/blockchain-cache.ts @@ -134,6 +134,7 @@ class BlockchainDataCache { this.proposalPages.clear(); this.proposalCounts.clear(); this.stakes.clear(); + this.minStakeAmounts.clear(); } getStats(): CacheStats { From 3cfa5aef563239f99bf82be950fd75999959244b Mon Sep 17 00:00:00 2001 From: 0xMosas Date: Sat, 11 Apr 2026 01:27:20 +0100 Subject: [PATCH 02/23] test(vitest): align @ alias with src root Update Vitest path aliasing so @/* resolves to frontend/src/* consistently in test runs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- frontend/vitest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 4bde83a7..c9f70ffa 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -29,7 +29,6 @@ export default defineConfig({ }, resolve: { alias: { - '@': resolve(configDir, '.'), '@/lib': resolve(configDir, 'src/lib'), '@/types': resolve(configDir, 'src/types'), '@/hooks': resolve(configDir, 'src/hooks'), @@ -37,6 +36,7 @@ export default defineConfig({ '@/services': resolve(configDir, 'src/services'), '@/components': resolve(configDir, 'components'), '@/utils': resolve(configDir, 'utils'), + '@': resolve(configDir, 'src'), }, }, }); From d87b9d915958dd45f3474f028a5bcb9224deecab Mon Sep 17 00:00:00 2001 From: 0xMosas Date: Sat, 11 Apr 2026 01:27:20 +0100 Subject: [PATCH 03/23] test(jsdom): add matchMedia and scrollIntoView shims Provide stable browser API shims required by app-level component tests under jsdom. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- frontend/src/test/vitest.setup.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/frontend/src/test/vitest.setup.ts b/frontend/src/test/vitest.setup.ts index bf637d33..291315cc 100644 --- a/frontend/src/test/vitest.setup.ts +++ b/frontend/src/test/vitest.setup.ts @@ -31,3 +31,23 @@ vi.mock('@stacks/connect-react', () => { Connect: ({ children }: { children: React.ReactNode }) => children, }; }); + +if (!window.matchMedia) { + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation((query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); +} + +if (!Element.prototype.scrollIntoView) { + Element.prototype.scrollIntoView = vi.fn(); +} From 57fe3df4bd727049ad035a97cf1566803c65a60d Mon Sep 17 00:00:00 2001 From: 0xMosas Date: Sat, 11 Apr 2026 01:27:20 +0100 Subject: [PATCH 04/23] test(app): fix hydration test mocks and module paths Correct mocked module paths and add required app dependency mocks for hydration suite reliability. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- frontend/src/__tests__/app-hydration.test.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/frontend/src/__tests__/app-hydration.test.tsx b/frontend/src/__tests__/app-hydration.test.tsx index 632b7799..2376c9c6 100644 --- a/frontend/src/__tests__/app-hydration.test.tsx +++ b/frontend/src/__tests__/app-hydration.test.tsx @@ -18,9 +18,28 @@ vi.mock('../store/wallet', () => ({ }), })); -vi.mock('../../components/Layout', () => ({ +vi.mock('../hooks/useKeyboardShortcuts', () => ({ + useKeyboardShortcuts: vi.fn(), + useNavigationShortcuts: vi.fn(() => ({ + goToDashboard: vi.fn(), + goToProposals: vi.fn(), + goToProfile: vi.fn(), + createProposal: vi.fn(), + })), +})); +vi.mock('../hooks/useCommandPalette', () => ({ + useCommandPalette: vi.fn(), + filterCommands: (commands: unknown[]) => commands, +})); +vi.mock('../components/Layout', () => ({ Layout: () =>
Layout
, })); +vi.mock('../components/OfflineBanner', () => ({ + OfflineBanner: () =>
, +})); +vi.mock('../../components/TransactionHistory', () => ({ + default: () =>
, +})); describe('App hydration initialization', () => { beforeEach(() => { From 9fc7fdb56d87c56a499345ba2b2e287f9c136be3 Mon Sep 17 00:00:00 2001 From: 0xMosas Date: Sat, 11 Apr 2026 01:27:20 +0100 Subject: [PATCH 05/23] test(app): stabilize command palette integration tests Fix wallet mock behavior, preserve keyboard shortcut hook behavior, and harden brittle assertions to match rendered structure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../__tests__/app-command-palette.test.tsx | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/frontend/src/__tests__/app-command-palette.test.tsx b/frontend/src/__tests__/app-command-palette.test.tsx index c7db3254..7ff8eef5 100644 --- a/frontend/src/__tests__/app-command-palette.test.tsx +++ b/frontend/src/__tests__/app-command-palette.test.tsx @@ -2,23 +2,41 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import App from '../App'; -import * as wallet from '../store/wallet'; -vi.mock('./store/wallet', () => ({ - useWalletStore: vi.fn(() => ({ - hydrate: vi.fn(), - })), +vi.mock('../store/wallet', () => ({ + useWalletStore: vi.fn((selector) => + selector({ + address: null, + connected: false, + loading: false, + connect: vi.fn(), + disconnect: vi.fn(), + hydrate: vi.fn(), + }), + ), })); +vi.mock('../hooks/useKeyboardShortcuts', async () => { + const actual = await vi.importActual('../hooks/useKeyboardShortcuts'); + return { + ...actual, + useNavigationShortcuts: vi.fn(() => ({ + goToDashboard: vi.fn(), + goToProposals: vi.fn(), + goToProfile: vi.fn(), + createProposal: vi.fn(), + })), + }; +}); vi.mock('../components/OfflineBanner', () => ({ OfflineBanner: () =>
, })); +vi.mock('../../components/TransactionHistory', () => ({ + default: () =>
, +})); describe('App - Command Palette Integration', () => { beforeEach(() => { vi.clearAllMocks(); - vi.mocked(wallet.useWalletStore).mockImplementation(() => ({ - hydrate: vi.fn(), - })); }); it('renders without command palette visible initially', () => { @@ -69,7 +87,7 @@ describe('App - Command Palette Integration', () => { await waitFor(() => { expect(screen.getByText('Go to Dashboard')).toBeInTheDocument(); expect(screen.getByText('Create New Proposal')).toBeInTheDocument(); - expect(screen.getByText('Browse Proposals')).toBeInTheDocument(); + expect(screen.getAllByText('Browse Proposals').length).toBeGreaterThan(0); }); }); @@ -184,7 +202,9 @@ describe('App - Command Palette Integration', () => { ).toBeInTheDocument(); }); - const backdrop = screen.getByRole('presentation', { hidden: true }); + const backdrop = document.querySelector('.fixed.inset-0.bg-black\\/40.z-40'); + expect(backdrop).toBeTruthy(); + if (!backdrop) return; fireEvent.click(backdrop); await waitFor(() => { From e207f3a5afa1c3ef2d1f950a1ace983db3836f0a Mon Sep 17 00:00:00 2001 From: 0xMosas Date: Wed, 15 Apr 2026 21:45:44 +0100 Subject: [PATCH 06/23] chore(frontend): trim ui imports --- frontend/components/TwitterShareButton.tsx | 3 +-- frontend/components/UserStatsDashboard.tsx | 10 +++------- frontend/components/VoteDelegationSystem.tsx | 2 +- frontend/components/VotingPowerConcentrationChart.tsx | 2 +- frontend/components/WarRoom.tsx | 1 - frontend/components/analytics/InsightsFeed.tsx | 2 +- frontend/components/analytics/PerformanceTracker.tsx | 4 ++-- 7 files changed, 9 insertions(+), 15 deletions(-) diff --git a/frontend/components/TwitterShareButton.tsx b/frontend/components/TwitterShareButton.tsx index f43865ee..69f98b06 100644 --- a/frontend/components/TwitterShareButton.tsx +++ b/frontend/components/TwitterShareButton.tsx @@ -2,10 +2,9 @@ interface TwitterShareButtonProps { proposalTitle: string; - proposalId: number; } -export default function TwitterShareButton({ proposalTitle, proposalId }: TwitterShareButtonProps) { +export default function TwitterShareButton({ proposalTitle }: TwitterShareButtonProps) { const handleShare = () => { const text = `Check out this proposal on SprintFund: "${proposalTitle}" 🚀\n\nVote now and help shape the future of decentralized funding!\n\n#SprintFund #StacksDAO #Web3`; const url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}`; diff --git a/frontend/components/UserStatsDashboard.tsx b/frontend/components/UserStatsDashboard.tsx index 8f6aa7c5..bedf234f 100644 --- a/frontend/components/UserStatsDashboard.tsx +++ b/frontend/components/UserStatsDashboard.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { Line, Doughnut, Bar } from 'react-chartjs-2'; import { Chart as ChartJS, @@ -41,11 +41,7 @@ interface CategoryData { color: string; } -interface UserStatsDashboardProps { - userAddress: string; -} - -export default function UserStatsDashboard({ userAddress }: UserStatsDashboardProps) { +export default function UserStatsDashboard() { const [timeRange, setTimeRange] = useState<'7d' | '30d' | '90d' | '1y'>('30d'); const [votingData] = useState([ { date: '2024-01-01', votes: 5, proposals: 3 }, @@ -338,7 +334,7 @@ export default function UserStatsDashboard({ userAddress }: UserStatsDashboardPr
🎯

Expand Categories

- You're most active in DeFi. Try voting on Infrastructure proposals to diversify! + You're most active in DeFi. Try voting on Infrastructure proposals to diversify!

diff --git a/frontend/components/VoteDelegationSystem.tsx b/frontend/components/VoteDelegationSystem.tsx index 7ffbbcfc..db779780 100644 --- a/frontend/components/VoteDelegationSystem.tsx +++ b/frontend/components/VoteDelegationSystem.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { motion } from 'framer-motion'; -import { Users, Shield, ArrowRight, Star, Search } from 'lucide-react'; +import { Shield, ArrowRight, Star, Search } from 'lucide-react'; const delegates = [ { address: 'SP12...ABCD', name: 'Mosa', reputation: 4.9, activeVotes: 156, categories: ['DeFi', 'Infrastructure'] }, diff --git a/frontend/components/VotingPowerConcentrationChart.tsx b/frontend/components/VotingPowerConcentrationChart.tsx index 2e92c8e4..e9209f67 100644 --- a/frontend/components/VotingPowerConcentrationChart.tsx +++ b/frontend/components/VotingPowerConcentrationChart.tsx @@ -1,6 +1,6 @@ 'use client'; -import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; +import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts'; import { useGovernanceAnalytics } from '@/hooks/useGovernanceAnalytics'; const COLORS = ['#8b5cf6', '#06b6d4', '#f59e0b', '#10b981']; diff --git a/frontend/components/WarRoom.tsx b/frontend/components/WarRoom.tsx index aca2c572..8e9654cf 100644 --- a/frontend/components/WarRoom.tsx +++ b/frontend/components/WarRoom.tsx @@ -2,7 +2,6 @@ import React from 'react'; import LiveVoteStream from './LiveVoteStream'; -import { motion } from 'framer-motion'; export default function WarRoom() { return ( diff --git a/frontend/components/analytics/InsightsFeed.tsx b/frontend/components/analytics/InsightsFeed.tsx index 34716f26..b98f2155 100644 --- a/frontend/components/analytics/InsightsFeed.tsx +++ b/frontend/components/analytics/InsightsFeed.tsx @@ -9,7 +9,7 @@ export default function InsightsFeed() { const [insights, setInsights] = useState([]); const [loading, setLoading] = useState(true); const [filter, setFilter] = useState<'all' | Insight['type']>('all'); - const [sortBy, setSortBy] = useState<'priority' | 'recency'>('priority'); + const [sortBy] = useState<'priority' | 'recency'>('priority'); const [search, setSearch] = useState(''); const [dismissedIds, setDismissedIds] = useState([]); const [selectedInsight, setSelectedInsight] = useState(null); diff --git a/frontend/components/analytics/PerformanceTracker.tsx b/frontend/components/analytics/PerformanceTracker.tsx index e892d9c5..ac9787d1 100644 --- a/frontend/components/analytics/PerformanceTracker.tsx +++ b/frontend/components/analytics/PerformanceTracker.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState } from 'react'; -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, AreaChart, Area } from 'recharts'; +import { XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, AreaChart, Area } from 'recharts'; export default function PerformanceTracker() { const [activeTab, setActiveTab] = useState<'overview' | 'achievements' | 'learning'>('overview'); @@ -146,7 +146,7 @@ export default function PerformanceTracker() {

- "You are currently performing in the top 5% of active proposers this quarter. Keep focusing on technical documentation." + "You are currently performing in the top 5% of active proposers this quarter. Keep focusing on technical documentation."

From 5c9452009002b0a748f4a2ee77dcb9a64448c09a Mon Sep 17 00:00:00 2001 From: 0xMosas Date: Thu, 16 Apr 2026 00:07:00 +0100 Subject: [PATCH 07/23] chore(frontend): clear lint tail --- frontend/components/ProposalDependencies.tsx | 11 ++++-- frontend/components/ProposalList.tsx | 38 +++++++++---------- frontend/components/ProposalTemplates.tsx | 8 ++-- .../components/TreasuryDiversification.tsx | 16 ++------ .../components/analytics/CommunityTab.tsx | 37 +----------------- .../analytics/ProposalComparator.tsx | 27 +------------ 6 files changed, 38 insertions(+), 99 deletions(-) diff --git a/frontend/components/ProposalDependencies.tsx b/frontend/components/ProposalDependencies.tsx index 2ea5d731..237660a7 100644 --- a/frontend/components/ProposalDependencies.tsx +++ b/frontend/components/ProposalDependencies.tsx @@ -14,7 +14,7 @@ interface ProposalDependenciesProps { currentProposalTitle: string; } -export default function ProposalDependencies({ proposalId: _proposalId, currentProposalTitle: _currentProposalTitle }: ProposalDependenciesProps) { +export default function ProposalDependencies({ proposalId, currentProposalTitle }: ProposalDependenciesProps) { const [dependencies, setDependencies] = useState([ { proposalId: 12, @@ -125,7 +125,12 @@ export default function ProposalDependencies({ proposalId: _proposalId, currentP return (
-

🔗 Proposal Dependencies

+
+

🔗 Proposal Dependencies

+

+ Proposal #{proposalId}: {currentProposalTitle} +

+