A quick vibe-coded site to test response times of PLC.directory mirrors (over 3 attempts)

Changes

Changed files
+350 -43
src
+2 -2
index.html
···
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
-
<title>186e5104-4723-467c-a30c-826aab8e6c65</title>
-
<meta name="description" content="Lovable Generated Project" />
+
<title>PLC Mirror Benchmark - ATProto</title>
+
<meta name="description" content="Benchmark and compare PLC.directory mirrors for ATProto DIDs" />
<meta name="author" content="Lovable" />
<meta property="og:title" content="186e5104-4723-467c-a30c-826aab8e6c65" />
+98
src/components/BenchmarkTable.tsx
···
+
import { BenchmarkResult } from "@/types/benchmark";
+
import {
+
Table,
+
TableBody,
+
TableCell,
+
TableHead,
+
TableHeader,
+
TableRow,
+
} from "@/components/ui/table";
+
import { Badge } from "@/components/ui/badge";
+
import { CheckCircle2, XCircle } from "lucide-react";
+
+
interface BenchmarkTableProps {
+
results: BenchmarkResult[];
+
}
+
+
export const BenchmarkTable = ({ results }: BenchmarkTableProps) => {
+
// Sort by response time (fastest first)
+
const sortedResults = [...results].sort((a, b) => {
+
if (a.status === "error" && b.status === "success") return 1;
+
if (a.status === "success" && b.status === "error") return -1;
+
return a.responseTime - b.responseTime;
+
});
+
+
return (
+
<div className="rounded-lg border border-border bg-card overflow-hidden">
+
<Table>
+
<TableHeader>
+
<TableRow className="bg-muted/50">
+
<TableHead className="font-semibold">Mirror</TableHead>
+
<TableHead className="font-semibold">URL</TableHead>
+
<TableHead className="font-semibold text-right">Response Time</TableHead>
+
<TableHead className="font-semibold">Status</TableHead>
+
<TableHead className="font-semibold">HTTP Code</TableHead>
+
</TableRow>
+
</TableHeader>
+
<TableBody>
+
{sortedResults.map((result, index) => (
+
<TableRow key={index} className="hover:bg-muted/30 transition-colors">
+
<TableCell className="font-medium">{result.mirrorName}</TableCell>
+
<TableCell className="font-mono text-sm text-muted-foreground">
+
{result.mirrorUrl}
+
</TableCell>
+
<TableCell className="text-right font-mono text-sm">
+
{result.status === "success" ? (
+
<span className="text-foreground font-medium">
+
{result.responseTime.toFixed(0)}ms
+
</span>
+
) : (
+
<span className="text-muted-foreground">—</span>
+
)}
+
</TableCell>
+
<TableCell>
+
{result.status === "success" ? (
+
<Badge
+
variant="outline"
+
className="bg-success/10 text-success border-success/20 gap-1"
+
>
+
<CheckCircle2 className="w-3 h-3" />
+
Success
+
</Badge>
+
) : (
+
<Badge
+
variant="outline"
+
className="bg-destructive/10 text-destructive border-destructive/20 gap-1"
+
>
+
<XCircle className="w-3 h-3" />
+
Error
+
</Badge>
+
)}
+
</TableCell>
+
<TableCell className="font-mono text-sm">
+
{result.statusCode ? (
+
<span
+
className={
+
result.statusCode >= 200 && result.statusCode < 300
+
? "text-success"
+
: "text-destructive"
+
}
+
>
+
{result.statusCode}
+
</span>
+
) : (
+
<span className="text-muted-foreground">—</span>
+
)}
+
</TableCell>
+
</TableRow>
+
))}
+
</TableBody>
+
</Table>
+
{sortedResults.length === 0 && (
+
<div className="text-center py-12 text-muted-foreground">
+
No results yet. Enter a DID and run the benchmark.
+
</div>
+
)}
+
</div>
+
);
+
};
+19
src/config/mirrors.ts
···
+
export interface Mirror {
+
name: string;
+
url: string;
+
}
+
+
export const mirrors: Mirror[] = [
+
{
+
name: "plc.directory",
+
url: "https://plc.directory",
+
},
+
{
+
name: "plc.bsky-mirror.dev",
+
url: "https://plc.bsky-mirror.dev",
+
},
+
{
+
name: "plc.us-east.host.bsky.network",
+
url: "https://plc.us-east.host.bsky.network",
+
},
+
];
+44 -36
src/index.css
···
@layer base {
:root {
-
--background: 0 0% 100%;
-
--foreground: 222.2 84% 4.9%;
+
--background: 220 15% 97%;
+
--foreground: 220 15% 15%;
--card: 0 0% 100%;
-
--card-foreground: 222.2 84% 4.9%;
+
--card-foreground: 220 15% 15%;
--popover: 0 0% 100%;
-
--popover-foreground: 222.2 84% 4.9%;
+
--popover-foreground: 220 15% 15%;
-
--primary: 222.2 47.4% 11.2%;
-
--primary-foreground: 210 40% 98%;
+
--primary: 217 91% 60%;
+
--primary-foreground: 0 0% 100%;
-
--secondary: 210 40% 96.1%;
-
--secondary-foreground: 222.2 47.4% 11.2%;
+
--secondary: 220 15% 95%;
+
--secondary-foreground: 220 15% 15%;
-
--muted: 210 40% 96.1%;
-
--muted-foreground: 215.4 16.3% 46.9%;
+
--muted: 220 15% 95%;
+
--muted-foreground: 220 10% 45%;
-
--accent: 210 40% 96.1%;
-
--accent-foreground: 222.2 47.4% 11.2%;
+
--accent: 217 91% 60%;
+
--accent-foreground: 0 0% 100%;
-
--destructive: 0 84.2% 60.2%;
-
--destructive-foreground: 210 40% 98%;
+
--destructive: 0 84% 60%;
+
--destructive-foreground: 0 0% 100%;
-
--border: 214.3 31.8% 91.4%;
-
--input: 214.3 31.8% 91.4%;
-
--ring: 222.2 84% 4.9%;
+
--border: 220 15% 88%;
+
--input: 220 15% 88%;
+
--ring: 217 91% 60%;
+
+
--success: 142 76% 36%;
+
--warning: 38 92% 50%;
+
--mono: 220 15% 25%;
--radius: 0.5rem;
···
}
.dark {
-
--background: 222.2 84% 4.9%;
-
--foreground: 210 40% 98%;
+
--background: 220 20% 10%;
+
--foreground: 220 15% 95%;
-
--card: 222.2 84% 4.9%;
-
--card-foreground: 210 40% 98%;
+
--card: 220 20% 12%;
+
--card-foreground: 220 15% 95%;
-
--popover: 222.2 84% 4.9%;
-
--popover-foreground: 210 40% 98%;
+
--popover: 220 20% 12%;
+
--popover-foreground: 220 15% 95%;
-
--primary: 210 40% 98%;
-
--primary-foreground: 222.2 47.4% 11.2%;
+
--primary: 217 91% 60%;
+
--primary-foreground: 0 0% 100%;
-
--secondary: 217.2 32.6% 17.5%;
-
--secondary-foreground: 210 40% 98%;
+
--secondary: 220 20% 16%;
+
--secondary-foreground: 220 15% 95%;
-
--muted: 217.2 32.6% 17.5%;
-
--muted-foreground: 215 20.2% 65.1%;
+
--muted: 220 20% 16%;
+
--muted-foreground: 220 10% 60%;
-
--accent: 217.2 32.6% 17.5%;
-
--accent-foreground: 210 40% 98%;
+
--accent: 217 91% 60%;
+
--accent-foreground: 0 0% 100%;
-
--destructive: 0 62.8% 30.6%;
-
--destructive-foreground: 210 40% 98%;
+
--destructive: 0 84% 60%;
+
--destructive-foreground: 0 0% 100%;
-
--border: 217.2 32.6% 17.5%;
-
--input: 217.2 32.6% 17.5%;
-
--ring: 212.7 26.8% 83.9%;
+
--border: 220 20% 20%;
+
--input: 220 20% 20%;
+
--ring: 217 91% 60%;
+
+
--success: 142 76% 36%;
+
--warning: 38 92% 50%;
+
--mono: 220 15% 85%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
+120 -5
src/pages/Index.tsx
···
-
// Update this page (the content is just a fallback if you fail to update the page)
+
import { useState } from "react";
+
import { Button } from "@/components/ui/button";
+
import { Input } from "@/components/ui/input";
+
import { BenchmarkTable } from "@/components/BenchmarkTable";
+
import { mirrors } from "@/config/mirrors";
+
import { benchmarkAllMirrors } from "@/utils/benchmark";
+
import { BenchmarkResult } from "@/types/benchmark";
+
import { toast } from "sonner";
+
import { Activity, Zap } from "lucide-react";
const Index = () => {
+
const [did, setDid] = useState("");
+
const [loading, setLoading] = useState(false);
+
const [results, setResults] = useState<BenchmarkResult[]>([]);
+
+
const handleBenchmark = async () => {
+
if (!did.trim()) {
+
toast.error("Please enter a DID");
+
return;
+
}
+
+
setLoading(true);
+
setResults([]);
+
+
try {
+
const benchmarkResults = await benchmarkAllMirrors(mirrors, did.trim());
+
setResults(benchmarkResults);
+
+
const successCount = benchmarkResults.filter(r => r.status === "success").length;
+
toast.success(`Benchmark complete! ${successCount}/${benchmarkResults.length} mirrors responded`);
+
} catch (error) {
+
toast.error("Failed to run benchmark");
+
console.error(error);
+
} finally {
+
setLoading(false);
+
}
+
};
+
+
const handleKeyPress = (e: React.KeyboardEvent) => {
+
if (e.key === "Enter" && !loading) {
+
handleBenchmark();
+
}
+
};
+
return (
-
<div className="flex min-h-screen items-center justify-center bg-background">
-
<div className="text-center">
-
<h1 className="mb-4 text-4xl font-bold">Welcome to Your Blank App</h1>
-
<p className="text-xl text-muted-foreground">Start building your amazing project here!</p>
+
<div className="min-h-screen bg-background py-8 px-4">
+
<div className="max-w-6xl mx-auto space-y-8">
+
{/* Header */}
+
<header className="text-center space-y-3">
+
<div className="flex items-center justify-center gap-2 text-primary">
+
<Activity className="w-8 h-8" />
+
<h1 className="text-4xl font-bold">PLC Mirror Benchmark</h1>
+
</div>
+
<p className="text-muted-foreground text-lg max-w-2xl mx-auto">
+
Test and compare response times across different PLC.directory mirrors
+
</p>
+
</header>
+
+
{/* Input Section */}
+
<div className="bg-card border border-border rounded-lg p-6 shadow-sm">
+
<div className="space-y-4">
+
<div>
+
<label htmlFor="did-input" className="block text-sm font-medium mb-2">
+
ATProto DID
+
</label>
+
<div className="flex gap-3">
+
<Input
+
id="did-input"
+
type="text"
+
placeholder="did:plc:example123..."
+
value={did}
+
onChange={(e) => setDid(e.target.value)}
+
onKeyPress={handleKeyPress}
+
className="flex-1 font-mono"
+
disabled={loading}
+
/>
+
<Button
+
onClick={handleBenchmark}
+
disabled={loading || !did.trim()}
+
className="gap-2"
+
>
+
<Zap className="w-4 h-4" />
+
{loading ? "Benchmarking..." : "Run Benchmark"}
+
</Button>
+
</div>
+
</div>
+
<div className="text-sm text-muted-foreground">
+
Testing {mirrors.length} mirrors: {mirrors.map(m => m.name).join(", ")}
+
</div>
+
</div>
+
</div>
+
+
{/* Results Section */}
+
{(results.length > 0 || loading) && (
+
<div className="space-y-4">
+
<h2 className="text-2xl font-semibold">Results</h2>
+
{loading ? (
+
<div className="bg-card border border-border rounded-lg p-12 text-center">
+
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary mb-4"></div>
+
<p className="text-muted-foreground">Benchmarking mirrors...</p>
+
</div>
+
) : (
+
<BenchmarkTable results={results} />
+
)}
+
</div>
+
)}
+
+
{/* Info Section */}
+
{results.length === 0 && !loading && (
+
<div className="bg-muted/30 border border-border rounded-lg p-6">
+
<h3 className="font-semibold mb-2">How to use:</h3>
+
<ol className="list-decimal list-inside space-y-1 text-sm text-muted-foreground">
+
<li>Enter an ATProto DID (e.g., did:plc:z72i7hdynmk6r22z27h6tvur)</li>
+
<li>Click "Run Benchmark" or press Enter</li>
+
<li>View response times and status for each mirror</li>
+
</ol>
+
<div className="mt-4 pt-4 border-t border-border">
+
<p className="text-sm text-muted-foreground">
+
Mirrors can be configured in <code className="bg-background px-2 py-1 rounded text-mono">src/config/mirrors.ts</code>
+
</p>
+
</div>
+
</div>
+
)}
</div>
</div>
);
+9
src/types/benchmark.ts
···
+
export interface BenchmarkResult {
+
mirrorName: string;
+
mirrorUrl: string;
+
responseTime: number;
+
status: "success" | "error";
+
statusCode?: number;
+
error?: string;
+
data?: any;
+
}
+55
src/utils/benchmark.ts
···
+
import { Mirror } from "@/config/mirrors";
+
import { BenchmarkResult } from "@/types/benchmark";
+
+
export const benchmarkMirror = async (
+
mirror: Mirror,
+
did: string
+
): Promise<BenchmarkResult> => {
+
const url = `${mirror.url}/${did}`;
+
const startTime = performance.now();
+
+
try {
+
const response = await fetch(url);
+
const endTime = performance.now();
+
const responseTime = endTime - startTime;
+
+
if (!response.ok) {
+
return {
+
mirrorName: mirror.name,
+
mirrorUrl: mirror.url,
+
responseTime,
+
status: "error",
+
statusCode: response.status,
+
error: `HTTP ${response.status}`,
+
};
+
}
+
+
const data = await response.json();
+
+
return {
+
mirrorName: mirror.name,
+
mirrorUrl: mirror.url,
+
responseTime,
+
status: "success",
+
statusCode: response.status,
+
data,
+
};
+
} catch (error) {
+
const endTime = performance.now();
+
return {
+
mirrorName: mirror.name,
+
mirrorUrl: mirror.url,
+
responseTime: endTime - startTime,
+
status: "error",
+
error: error instanceof Error ? error.message : "Unknown error",
+
};
+
}
+
};
+
+
export const benchmarkAllMirrors = async (
+
mirrors: Mirror[],
+
did: string
+
): Promise<BenchmarkResult[]> => {
+
const promises = mirrors.map((mirror) => benchmarkMirror(mirror, did));
+
return Promise.all(promises);
+
};
+3
tailwind.config.ts
···
border: "hsl(var(--sidebar-border))",
ring: "hsl(var(--sidebar-ring))",
},
+
success: "hsl(var(--success))",
+
warning: "hsl(var(--warning))",
+
mono: "hsl(var(--mono))",
},
borderRadius: {
lg: "var(--radius)",