Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 28 additions & 0 deletions IMPROVEMENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
# PingDiff Improvement Log

## 2026-03-20 — Bug Fix: Per-page metadata for dashboard, download, and community

All three main pages used `"use client"` at the top level, which blocks Next.js from reading the
`metadata` export. Every page fell back to the generic homepage title and description — so sharing
any page on Discord, Twitter, or iMessage showed "PingDiff - Test Your Game Server Connection"
regardless of which page it was. The `template: "%s | PingDiff"` in `layout.tsx` was dead code.

Fixed by splitting each page into a thin server wrapper (`page.tsx`, exports metadata) and a
client component (`*Client.tsx`, holds all interactive state). No logic changed — pure structural
refactor following the standard App Router pattern. Each page now has a distinct title,
description, and og:title/og:description for accurate social previews.

**Files changed:** `web/src/app/dashboard/page.tsx`, `web/src/app/dashboard/DashboardClient.tsx` (new), `web/src/app/download/page.tsx`, `web/src/app/download/DownloadClient.tsx` (new), `web/src/app/community/page.tsx`, `web/src/app/community/CommunityClient.tsx` (new)
**Lines:** +934 / -890

## 2026-03-20 — Accessibility: ARIA labels, table semantics, and skip navigation

Comprehensive a11y pass across the dashboard, navbar, and secondary pages. The site had no named navigation landmark, no skip links on 3 of 4 pages, tables without column scope attributes, charts completely invisible to assistive technology, and stat cards that conveyed quality purely through color (WCAG 1.4.1 violation). All fixed without new dependencies.

Dashboard: skip link + main-content anchor, role="region" on stats grid, aria-label on each stat card with text description, aria-hidden on decorative icons, role="img" + aria-label on both charts, aria-label on table element, scope="col" on all th elements, time element for timestamps, aria-label on ping/loss cells so quality is communicated in text not just color.

Navbar: aria-label="Main navigation" on nav element, aria-haspopup on mobile toggle, role="menu" on mobile menu container.

Community and Download pages: both were missing skip-to-content links and main-content anchor targets entirely.

**Files changed:** `web/src/app/dashboard/page.tsx`, `web/src/components/Navbar.tsx`, `web/src/app/community/page.tsx`, `web/src/app/download/page.tsx`
**Lines:** +64 / -30

## 2026-03-19 — Performance: Memoize dashboard derived state

All derived values on the dashboard (filteredResults, avgPing, avgPacketLoss, avgJitter, regions, chartData, serverChartData) were being recomputed inline on every React render — including renders triggered by unrelated state changes like the loading flag toggling off. Wrapped each value in useMemo with the tightest possible dependency array, eliminating 5 O(n) reduce passes and 2 groupBy passes on every extraneous render. At current scale the savings are modest; at the 500-1000 result range the dashboard would hit without this change the difference is measurable. The memoized structure also makes data dependencies explicit and auditable at a glance.
Expand Down
83 changes: 83 additions & 0 deletions web/src/app/community/CommunityClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"use client";

import { MessageSquare, ThumbsUp, Users, Construction } from "lucide-react";
import { Navbar } from "@/components/Navbar";
import { Footer } from "@/components/Footer";

export default function CommunityClient() {
return (
<div className="min-h-screen">
<a href="#main-content" className="skip-to-content focus-ring">
Skip to main content
</a>
<Navbar />

<main id="main-content" className="max-w-6xl mx-auto px-4 py-16">
{/* Coming Soon Banner */}
<div className="text-center mb-16">
<div className="inline-flex items-center justify-center w-20 h-20 bg-yellow-500/20 rounded-2xl mb-6">
<Construction className="w-10 h-10 text-yellow-500" />
</div>
<h1 className="text-4xl font-bold mb-4">Community Hub</h1>
<p className="text-zinc-400 text-lg max-w-xl mx-auto">
Share tips, compare results, and help other players find the best servers.
</p>
<div className="mt-6 inline-flex items-center gap-2 bg-yellow-500/10 border border-yellow-500/20 rounded-full px-4 py-2">
<span className="text-yellow-400 text-sm font-medium">Coming Soon</span>
</div>
</div>

{/* Preview Features */}
<div className="grid md:grid-cols-3 gap-6 mb-16">
<div className="bg-zinc-900/50 border border-zinc-800 rounded-xl p-6 opacity-60">
<div className="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center mb-4">
<MessageSquare className="w-6 h-6 text-blue-500" />
</div>
<h3 className="text-xl font-semibold mb-2">ISP Tips</h3>
<p className="text-zinc-400">
Share and discover tips for optimizing your connection based on your ISP and region.
</p>
</div>

<div className="bg-zinc-900/50 border border-zinc-800 rounded-xl p-6 opacity-60">
<div className="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mb-4">
<ThumbsUp className="w-6 h-6 text-green-500" />
</div>
<h3 className="text-xl font-semibold mb-2">Upvote System</h3>
<p className="text-zinc-400">
Vote on the most helpful tips to surface the best advice for each region.
</p>
</div>

<div className="bg-zinc-900/50 border border-zinc-800 rounded-xl p-6 opacity-60">
<div className="w-12 h-12 bg-purple-500/20 rounded-xl flex items-center justify-center mb-4">
<Users className="w-6 h-6 text-purple-500" />
</div>
<h3 className="text-xl font-semibold mb-2">Leaderboards</h3>
<p className="text-zinc-400">
See the best ping results by region, ISP, and server location.
</p>
</div>
</div>

{/* CTA */}
<div className="text-center bg-zinc-900/50 border border-zinc-800 rounded-2xl p-8">
<h2 className="text-2xl font-bold mb-4">Want to be notified when Community launches?</h2>
<p className="text-zinc-400 mb-6">
Star our GitHub repo to get updates on new features.
</p>
<a
href="https://github.com/bokiko/pingdiff"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 btn-primary px-6 py-3 rounded-xl font-medium"
>
Star on GitHub
</a>
</div>
</main>

<Footer />
</div>
);
}
92 changes: 12 additions & 80 deletions web/src/app/community/page.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,15 @@
"use client";

import { MessageSquare, ThumbsUp, Users, Construction } from "lucide-react";
import { Navbar } from "@/components/Navbar";
import { Footer } from "@/components/Footer";
import type { Metadata } from "next";
import CommunityClient from "./CommunityClient";

export const metadata: Metadata = {
title: "Community",
description: "Share connection tips, compare ping results with other players, and find the best game servers for your region.",
openGraph: {
title: "PingDiff Community Hub",
description: "Share connection tips, compare ping results with other players, and find the best game servers for your region.",
},
};

export default function CommunityPage() {
return (
<div className="min-h-screen">
<a href="#main-content" className="skip-to-content focus-ring">
Skip to main content
</a>
<Navbar />

<main id="main-content" className="max-w-6xl mx-auto px-4 py-16">
{/* Coming Soon Banner */}
<div className="text-center mb-16">
<div className="inline-flex items-center justify-center w-20 h-20 bg-yellow-500/20 rounded-2xl mb-6">
<Construction className="w-10 h-10 text-yellow-500" />
</div>
<h1 className="text-4xl font-bold mb-4">Community Hub</h1>
<p className="text-zinc-400 text-lg max-w-xl mx-auto">
Share tips, compare results, and help other players find the best servers.
</p>
<div className="mt-6 inline-flex items-center gap-2 bg-yellow-500/10 border border-yellow-500/20 rounded-full px-4 py-2">
<span className="text-yellow-400 text-sm font-medium">Coming Soon</span>
</div>
</div>

{/* Preview Features */}
<div className="grid md:grid-cols-3 gap-6 mb-16">
<div className="bg-zinc-900/50 border border-zinc-800 rounded-xl p-6 opacity-60">
<div className="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center mb-4">
<MessageSquare className="w-6 h-6 text-blue-500" />
</div>
<h3 className="text-xl font-semibold mb-2">ISP Tips</h3>
<p className="text-zinc-400">
Share and discover tips for optimizing your connection based on your ISP and region.
</p>
</div>

<div className="bg-zinc-900/50 border border-zinc-800 rounded-xl p-6 opacity-60">
<div className="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mb-4">
<ThumbsUp className="w-6 h-6 text-green-500" />
</div>
<h3 className="text-xl font-semibold mb-2">Upvote System</h3>
<p className="text-zinc-400">
Vote on the most helpful tips to surface the best advice for each region.
</p>
</div>

<div className="bg-zinc-900/50 border border-zinc-800 rounded-xl p-6 opacity-60">
<div className="w-12 h-12 bg-purple-500/20 rounded-xl flex items-center justify-center mb-4">
<Users className="w-6 h-6 text-purple-500" />
</div>
<h3 className="text-xl font-semibold mb-2">Leaderboards</h3>
<p className="text-zinc-400">
See the best ping results by region, ISP, and server location.
</p>
</div>
</div>

{/* CTA */}
<div className="text-center bg-zinc-900/50 border border-zinc-800 rounded-2xl p-8">
<h2 className="text-2xl font-bold mb-4">Want to be notified when Community launches?</h2>
<p className="text-zinc-400 mb-6">
Star our GitHub repo to get updates on new features.
</p>
<a
href="https://github.com/bokiko/pingdiff"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 btn-primary px-6 py-3 rounded-xl font-medium"
>
Star on GitHub
</a>
</div>
</main>

<Footer />
</div>
);
return <CommunityClient />;
}
Loading
Loading