Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
557765a
feat(theme): design-token foundation — real Inter font, semantic toke…
claude Jul 4, 2026
10a8be1
perf: lazy crypto stack, memoized wallet context, on-demand export libs
claude Jul 4, 2026
5e22ede
refactor(ui): replace hardcoded palette colors with semantic design t…
claude Jul 4, 2026
20e1842
feat(ux): route-level loading/error states and unified loading visuals
claude Jul 4, 2026
1a9582c
refactor: shared formatters, wallet guard, copy hook, a11y and title …
claude Jul 4, 2026
5b8a65f
fix(a11y): give the connection-progress dialogs screen-reader titles
claude Jul 4, 2026
d1e7dee
build: enforce TypeScript errors at build time
claude Jul 5, 2026
012af07
perf: defer wallet-cache localStorage writes and reuse the Nostr rela…
claude Jul 5, 2026
d534d61
perf(transactions): virtualize the transaction list
claude Jul 5, 2026
21fc61c
refactor(layout): decompose the 845-line app layout into components/l…
claude Jul 5, 2026
cfb2561
perf(context): split wallet context into scoped data/actions/AI/Nostr…
claude Jul 5, 2026
f3f10cf
perf(connect): batch discovery server actions — ~6x faster wallet con…
claude Jul 5, 2026
c290cf3
refactor(guard): drop dead nullish fallback in useWalletDataGuard
claude Jul 7, 2026
6968d6f
fix(wallet-cache): flush deferred writes on pagehide/visibilitychange
claude Jul 7, 2026
3d85d26
Merge pull request #837 from BitSleuthAI/claude/bitsleuth-analyzer-re…
jamespepper81 Jul 7, 2026
cb06cb0
Fix for Unused variable, import, function or class
jamespepper81 Jul 7, 2026
f032f07
Merge pull request #838 from BitSleuthAI/finding-autofix-60
jamespepper81 Jul 7, 2026
368d78e
Fix for Unused variable, import, function or class
jamespepper81 Jul 7, 2026
77c3527
Merge pull request #839 from BitSleuthAI/finding-autofix-63
jamespepper81 Jul 7, 2026
6bd159d
Fix for Unused variable, import, function or class
jamespepper81 Jul 7, 2026
8eaf1e4
Merge pull request #840 from BitSleuthAI/finding-autofix-58
jamespepper81 Jul 7, 2026
f28061e
Fix for Unused variable, import, function or class
jamespepper81 Jul 7, 2026
0d5fb4a
Potential fix for pull request finding 'Unused variable, import, func…
jamespepper81 Jul 7, 2026
4fc899c
Potential fix for pull request finding 'Unused variable, import, func…
jamespepper81 Jul 7, 2026
2455aa7
Merge pull request #841 from BitSleuthAI/finding-autofix-51
jamespepper81 Jul 7, 2026
76f14d0
Fix for Unused variable, import, function or class
jamespepper81 Jul 7, 2026
bf3d6b8
Potential fix for pull request finding 'Unused variable, import, func…
jamespepper81 Jul 7, 2026
f9167f5
Potential fix for pull request finding 'Unused variable, import, func…
jamespepper81 Jul 7, 2026
2990c8d
Merge pull request #842 from BitSleuthAI/finding-autofix-56
jamespepper81 Jul 7, 2026
3aa3c74
Fix for Unused variable, import, function or class
jamespepper81 Jul 7, 2026
ec84fbc
Merge pull request #843 from BitSleuthAI/finding-autofix-59
jamespepper81 Jul 7, 2026
0c895ab
Fix for Unused variable, import, function or class
jamespepper81 Jul 7, 2026
8efef94
Merge pull request #844 from BitSleuthAI/finding-autofix-57
jamespepper81 Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
Expand Down
5 changes: 1 addition & 4 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
typescript: {
ignoreBuildErrors: true,
},
// Development-specific optimizations
...(process.env.NODE_ENV === 'development' && {
onDemandEntries: {
Expand Down Expand Up @@ -150,7 +147,7 @@ const nextConfig: NextConfig = {
// Updated experimental flags for Next.js 16
experimental: {
webVitalsAttribution: ['CLS', 'LCP'],
optimizePackageImports: ['@radix-ui/react-icons', 'lucide-react'],
optimizePackageImports: ['@radix-ui/react-icons', 'lucide-react', 'recharts'],
},
// Turbopack configuration (replaces webpack)
// Turbopack has native WASM support - no custom config needed
Expand Down
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toast": "^1.2.15",
"@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-virtual": "^3.14.5",
"@vercel/analytics": "^2.0.1",
"@vercel/speed-insights": "^2.0.0",
"bip32": "^5.0.1",
Expand Down
37 changes: 15 additions & 22 deletions src/app/(app)/address/[address]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import {
} from '@/components/ui/table';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { ArrowLeft, Bitcoin, CircleAlert, ArrowUpRight, ArrowDownLeft, ArrowLeftRight } from 'lucide-react';
import { EmptyState } from '@/components/ui/empty-state';
import { ArrowLeft, Bitcoin, ArrowUpRight, ArrowDownLeft, ArrowLeftRight } from 'lucide-react';
import { useWallet } from '@/contexts/wallet-context';
import { formatCurrency as formatCurrencyValue } from '@/lib/format';
import { FullPageLoader, ErrorDisplay } from '@/components/ui/loader';
import { cn } from '@/lib/utils';
import type { Transaction, AddressInfo } from '@/lib/types';
Expand All @@ -32,12 +34,7 @@ const TransactionRow = React.memo(({ tx, fiatPrice, currency }: { tx: Transactio
const isReceived = tx.type === 'Received';
const fiatAmount = Math.abs(tx.btc * fiatPrice);

const formatCurrency = (value: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
}).format(value);
};
const formatCurrency = (value: number) => formatCurrencyValue(value, currency);

return (
<TableRow className="hover:bg-muted/50 transition-colors">
Expand Down Expand Up @@ -80,7 +77,7 @@ const TransactionRow = React.memo(({ tx, fiatPrice, currency }: { tx: Transactio
className={cn(
'text-sm',
tx.status === 'Confirmed' && 'border-chart-positive/40 text-chart-positive',
tx.status === 'Pending' && 'border-yellow-500/40 text-yellow-500'
tx.status === 'Pending' && 'border-warning/40 text-warning'
)}
>
{tx.status}
Expand Down Expand Up @@ -152,29 +149,25 @@ export default function AddressDetailsPage() {
loadData();
}, [address, loadData]);

const formatCurrency = (value: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
}).format(value);
};
const formatCurrency = (value: number) => formatCurrencyValue(value, currency);

if (isWalletLoading || pageIsLoading) return <FullPageLoader />;
if (walletError && !pageData) return <ErrorDisplay message={walletError} />;

if (pageError) {
return (
<div className="flex flex-col items-center justify-center gap-4 text-center">
<CircleAlert className="h-12 w-12 text-destructive" />
<h1 className="text-2xl font-bold">Address Not Found</h1>
<p className="text-muted-foreground">{pageError}</p>
<Button asChild onClick={() => router.back()}>
<EmptyState
title="Address Not Found"
message={pageError}
action={
<Button asChild>
<Link href="/security">
<ArrowLeft className="mr-2 h-4 w-4" />
Back
</Link>
</Button>
</div>
}
/>
);
}

Expand Down Expand Up @@ -209,7 +202,7 @@ export default function AddressDetailsPage() {
</CardHeader>
<CardContent>
<Card className="border-2 shadow-sm">
<CardHeader className="bg-gradient-to-br from-blue-500/5 via-transparent to-transparent">
<CardHeader className="bg-gradient-to-br from-info/5 via-transparent to-transparent">
<CardTitle className="text-base font-medium">Address Balance</CardTitle>
</CardHeader>
<CardContent>
Expand All @@ -225,7 +218,7 @@ export default function AddressDetailsPage() {
</Card>

<Card className="border-2 shadow-md">
<CardHeader className="bg-gradient-to-br from-emerald-500/5 via-transparent to-transparent border-b">
<CardHeader className="bg-gradient-to-br from-success/5 via-transparent to-transparent border-b">
<CardTitle className="flex items-center gap-2">
<IconContainer variant="emerald">
<ArrowLeftRight className="h-5 w-5" />
Expand Down
33 changes: 15 additions & 18 deletions src/app/(app)/analysis/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
TableRow,
} from '@/components/ui/table';
import { useWallet } from '@/contexts/wallet-context';
import { FullPageLoader, ErrorDisplay } from '@/components/ui/loader';
import { useWalletDataGuard } from '@/components/wallet-data-guard';
import { Area, AreaChart, Bar, BarChart, CartesianGrid, Legend, Scatter, ScatterChart, XAxis, YAxis, Tooltip, ZAxis } from 'recharts';
import { useMemo } from 'react';
import { TrendingUp } from 'lucide-react';
Expand Down Expand Up @@ -66,7 +66,8 @@ const btcToSatsFormatter = (value: any) => {


export default function AnalysisPage() {
const { data, isLoading, error, activeXpub: xpub, fiatPrice, currency, isDiscovering, discoveryProgress } = useWallet();
const { data, fiatPrice, currency, isDiscovering, discoveryProgress } = useWallet();
const walletGuard = useWalletDataGuard();

const balanceChartData = useMemo(() => {
if (!data || !data.transactions || data.transactions.length === 0) {
Expand Down Expand Up @@ -187,12 +188,8 @@ export default function AnalysisPage() {
return Object.values(feesByMonth).sort((a,b) => a.key.localeCompare(b.key));
}, [data]);

const hasBlockingError = !!error && !data;

if (!xpub) return <FullPageLoader />;
if (isLoading && !data) return <FullPageLoader />;
if (hasBlockingError) return <ErrorDisplay message={error ?? 'Unable to load wallet data.'} />;
if (!data) return <ErrorDisplay message="No wallet data found. Please connect a wallet." />;
if (walletGuard) return walletGuard;
if (!data) return null;

if (balanceChartData.length <= 2) { // only has start and end points
return (
Expand Down Expand Up @@ -231,21 +228,21 @@ export default function AnalysisPage() {
<div className="flex flex-col gap-4 sm:gap-6">
{/* Progressive Discovery Status */}
{isDiscovering && discoveryProgress && (
<div className="bg-gradient-to-r from-blue-50 to-purple-50 dark:from-blue-950/30 dark:to-purple-950/30 border-2 border-blue-200 dark:border-blue-800 rounded-lg px-4 py-3 shadow-md">
<div className="bg-gradient-to-r from-info/10 to-chart-purple/10 dark:from-info/30 dark:to-chart-purple/30 border-2 border-info/30 dark:border-info/40 rounded-lg px-4 py-3 shadow-md">
<div className="flex items-start gap-3">
<LoaderCircle className="h-5 w-5 animate-spin text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<LoaderCircle className="h-5 w-5 animate-spin text-info mt-0.5 flex-shrink-0" />
<div className="flex-1 space-y-2">
<div className="flex items-center justify-between">
<p className="text-sm font-semibold text-blue-900 dark:text-blue-100">
<p className="text-sm font-semibold text-info">
🔍 Discovering addresses... Charts updating with new data
</p>
<p className="text-xs text-blue-700 dark:text-blue-300 font-mono">
<p className="text-xs text-info font-mono">
{discoveryProgress.addressesWithActivity} addresses
</p>
</div>
<div className="flex items-center gap-2">
<Progress value={(discoveryProgress.addressesChecked / (discoveryProgress.addressesChecked + 20)) * 100} className="h-1.5" />
<span className="text-xs text-blue-700 dark:text-blue-300 whitespace-nowrap">
<span className="text-xs text-info whitespace-nowrap">
{discoveryProgress.addressesChecked} checked
</span>
</div>
Expand Down Expand Up @@ -322,7 +319,7 @@ export default function AnalysisPage() {
</Card>

<Card className="border-2 shadow-md">
<CardHeader className="bg-gradient-to-br from-emerald-500/5 via-transparent to-transparent border-b">
<CardHeader className="bg-gradient-to-br from-success/5 via-transparent to-transparent border-b">
<CardTitle className="text-base sm:text-lg flex items-center gap-2">
<IconContainer variant="emerald">
<ChartColumn className="h-5 w-5" />
Expand Down Expand Up @@ -360,7 +357,7 @@ export default function AnalysisPage() {
</Card>

<Card className="border-2 shadow-md">
<CardHeader className="bg-gradient-to-br from-orange-500/5 via-transparent to-transparent border-b">
<CardHeader className="bg-gradient-to-br from-primary/5 via-transparent to-transparent border-b">
<CardTitle className="text-base sm:text-lg flex items-center gap-2">
<IconContainer variant="orange">
<ChartPie className="h-5 w-5" />
Expand All @@ -383,7 +380,7 @@ export default function AnalysisPage() {
</Card>

<Card className="border-2 shadow-md">
<CardHeader className="bg-gradient-to-br from-rose-500/5 via-transparent to-transparent border-b">
<CardHeader className="bg-gradient-to-br from-chart-negative/5 via-transparent to-transparent border-b">
<CardTitle className="text-base sm:text-lg flex items-center gap-2">
<IconContainer variant="rose">
<DollarSign className="h-5 w-5" />
Expand Down Expand Up @@ -425,7 +422,7 @@ export default function AnalysisPage() {
</Card>

<Card className="border-2 shadow-md">
<CardHeader className="bg-gradient-to-br from-purple-500/5 via-transparent to-transparent border-b">
<CardHeader className="bg-gradient-to-br from-chart-purple/5 via-transparent to-transparent border-b">
<CardTitle className="text-base sm:text-lg flex items-center gap-2">
<IconContainer variant="purple">
<Activity className="h-5 w-5" />
Expand Down Expand Up @@ -455,7 +452,7 @@ export default function AnalysisPage() {
</Card>

<Card className="lg:col-span-2 border-2 shadow-md">
<CardHeader className="bg-gradient-to-br from-amber-500/5 via-transparent to-transparent border-b">
<CardHeader className="bg-gradient-to-br from-warning/5 via-transparent to-transparent border-b">
<CardTitle className="text-base sm:text-lg flex items-center gap-2">
<IconContainer variant="amber">
<Coins className="h-5 w-5" />
Expand Down
24 changes: 6 additions & 18 deletions src/app/(app)/block/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import {
TableRow,
} from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { EmptyState } from '@/components/ui/empty-state';
import { IconContainer } from '@/components/ui/icon-container';
import { ArrowLeft, CircleAlert, Copy, Box, Download, LoaderCircle } from 'lucide-react';
import { FullPageLoader, ErrorDisplay } from '@/components/ui/loader';
import { useToast } from '@/hooks/use-toast';
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard';
import type { BlockDetails, BlockTransaction } from '@/lib/types';
import { getBlockDetails } from '@/lib/mempool';

Expand Down Expand Up @@ -98,27 +100,13 @@ export default function BlockDetailsPage() {
};


const handleCopy = (text: string) => {
navigator.clipboard.writeText(text);
toast({
title: 'Copied to clipboard',
description: text,
});
};
const handleCopy = useCopyToClipboard();

if (pageIsLoading) return <FullPageLoader />;

if (pageError) {
return (
<div className="flex flex-col items-center justify-center gap-4 text-center">
<CircleAlert className="h-12 w-12 text-destructive" />
<h1 className="text-2xl font-bold">Block Not Found</h1>
<p className="text-muted-foreground">{pageError}</p>
<Button onClick={() => router.back()}>
<ArrowLeft className="mr-2 h-4 w-4" />
Back
</Button>
</div>
<EmptyState title="Block Not Found" message={pageError} onBack={() => router.back()} />
);
}

Expand Down Expand Up @@ -158,7 +146,7 @@ export default function BlockDetailsPage() {
</Card>

<Card className="border-2 shadow-md">
<CardHeader className="bg-gradient-to-br from-blue-500/5 via-transparent to-transparent border-b">
<CardHeader className="bg-gradient-to-br from-info/5 via-transparent to-transparent border-b">
<CardTitle className="text-base sm:text-lg flex items-center gap-2">
<IconContainer variant="blue">
<CircleAlert className="h-5 w-5" />
Expand All @@ -184,7 +172,7 @@ export default function BlockDetailsPage() {
</Card>

<Card className="border-2 shadow-md">
<CardHeader className="bg-gradient-to-br from-emerald-500/5 via-transparent to-transparent border-b">
<CardHeader className="bg-gradient-to-br from-success/5 via-transparent to-transparent border-b">
<CardTitle className="text-base sm:text-lg flex items-center gap-2">
<IconContainer variant="emerald">
<Download className="h-5 w-5" />
Expand Down
25 changes: 8 additions & 17 deletions src/app/(app)/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { summarizeAddress } from '@/ai/flows/summarize-address';
import { cn } from '@/lib/utils';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { useWallet } from '@/contexts/wallet-context';
import { useWalletData, useWalletActions, useWalletAi } from '@/contexts/wallet-context';
import { FullPageLoader, ErrorDisplay } from '@/components/ui/loader';
import { AiChart } from '@/components/ui/ai-chart';
import type { Message, WalletData } from '@/lib/types';
Expand Down Expand Up @@ -183,18 +183,9 @@ export default function ChatPage() {
const { track } = useAnalytics();
const [isAiLoading, setAiLoading] = useState(false);
const scrollAreaRef = useRef<HTMLDivElement>(null);
const {
data: walletData,
isLoading: isWalletLoading,
isLoadingAiContent,
aiContentError,
refreshAiContent,
error: walletError,
activeXpub: xpub,
messages,
setMessages,
suggestions: masterSuggestions,
} = useWallet();
const { data: walletData, isLoading: isWalletLoading, error: walletError, activeXpub: xpub } = useWalletData();
const { refreshAiContent } = useWalletActions();
const { isLoadingAiContent, aiContentError, messages, setMessages, suggestions: masterSuggestions } = useWalletAi();

const [placeholder, setPlaceholder] = useState(fallbackSuggestions[0]);
const [displayedSuggestions, setDisplayedSuggestions] = useState<string[]>([]);
Expand Down Expand Up @@ -468,10 +459,10 @@ export default function ChatPage() {
<div className="mx-auto max-w-3xl space-y-4 sm:space-y-6 pt-4">
{/* AI Insights Loading Indicator */}
{isLoadingAiContent && (
<Alert className="shadow-md bg-blue-50 dark:bg-blue-950/30 border-blue-200 dark:border-blue-800">
<CircleDashed className="h-5 w-5 animate-spin text-blue-600 dark:text-blue-400" />
<AlertTitle className="text-blue-900 dark:text-blue-100">Generating AI insights...</AlertTitle>
<AlertDescription className="text-blue-800 dark:text-blue-200">
<Alert className="shadow-md bg-info/15 dark:bg-info/30 border-info/30 dark:border-info/40">
<CircleDashed className="h-5 w-5 animate-spin text-info" />
<AlertTitle className="text-info">Generating AI insights...</AlertTitle>
<AlertDescription className="text-info">
Your wallet is loaded. AI is analyzing your transactions and will provide personalized insights momentarily.
</AlertDescription>
</Alert>
Expand Down
Loading