Files
deckyvault/docs/superpowers/specs/2026-04-28-games-list-contact-seo-design.md
T

168 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Games List, Contact Page & SEO — Design Spec
**Date:** 2026-04-28
**Status:** Approved
---
## 1. Games List Page (`/games`)
### Architecture
Server component for initial render + SEO metadata, client component for interactive infinite scroll, search, and filtering.
### Data Flow
1. `page.tsx` (server) fetches first 24 games with total count via direct DB query
2. `games-page-client.tsx` (client) renders compact grid + search/filter bar
3. As user scrolls near bottom, `IntersectionObserver` triggers fetch to `/api/games?offset=N&limit=24&search=...&filter_...`
4. URL search params update reflectively: `?q=term&genre=Action&device=steam-deck&sort=benchmarks`
### Card Design (Compact)
Each card displays:
- Game cover image (capsule image, portrait 2:3 ratio, `next/image` with lazy loading)
- Title (12 lines, truncated with `line-clamp-2`)
- 12 compact stat badges:
- Benchmark count (icon + number)
- Avg FPS badge (if data exists) or "No data yet" muted text
- Deck status pill (Native/Proton/Unsupported) — from `gamePlatformSupport`
- On hover: subtle border highlight + scale, links to `/game/{id}`
### Filters
- **Search**: Debounced text input (300ms), uses `?search=` on `/api/games`
- **Genre**: Multi-select pill buttons derived from aggregating genres from initial game batch
- **Device**: Pill buttons matching existing devices page pattern (all hardware slugs)
- **Sort**: Dropdown — Name AZ, Most Benchmarks, Recently Added
### Performance
- Initial 24 items server-rendered for SEO crawlability
- Subsequent pages fetched client-side (24 per page)
- `IntersectionObserver` on sentinel element at bottom of grid
- `next/image` with `sizes` attribute for responsive srcsets
- Total count tracked to stop loading when exhausted
- Suspense boundary around grid for streaming initial data
### SEO
- `generateMetadata()`: title "Games — DeckyVault", description, OG tags, canonical `https://deckyvault.xyz/games`
- JSON-LD `ItemList` schema with initial 24 games
- Page is indexable (unlike `/search`)
### File Structure
```
app/games/
page.tsx — Server component: fetch initial games, metadata, JSON-LD
games-page-client.tsx — Client component: grid, infinite scroll, search, filters
```
---
## 2. Contact / Report Page (`/contact`)
### Architecture
Client component for the form UI, server-side Elysia API route for Discord webhook submission.
### Form Fields
| Field | Type | Required | Notes |
|-------|------|----------|-------|
| Category | Radio/pill select | Yes | Bug Report, Game Data Issue, User Report, Feature Request, General Feedback, Database Error |
| Name | Text | No | Pre-filled from session if logged in |
| Email | Text | No | Pre-filled from session if logged in |
| Subject | Text | Yes | Max 200 chars |
| Message | Textarea | Yes | Max 2000 chars |
| Game URL | Text | Conditional | Shown only for "Game Data Issue" category. Validates `/game/` URL pattern |
| Honeypot | Hidden text | No | Must remain empty (anti-bot) |
| Timestamp | Hidden | No | Client-side submission start time (anti-bot timing check) |
### Discord Webhook Integration
New Elysia route: `POST /api/contact`
**Payload → Discord Embed mapping:**
- Category → embed color (🔴 red: Bug/DB Error, 🟡 yellow: Game Data, 🔵 blue: User Report, 🟢 green: Feature Request, ⚪ grey: General)
- Name, Email, Subject, Message → embed fields
- Game URL → link field (when present)
- User ID (if authenticated) → footer field
- IP hash (SHA-256 truncated) → footer field for tracking
**Environment variable:** `DISCORD_WEBHOOK_URL` added to `.env.example`
### Anti-Abuse Measures
1. **Honeypot field**: Hidden input that bots fill; server rejects if non-empty
2. **Timing check**: Server rejects submissions where timestamp < 3 seconds ago (bots submit instantly)
3. **IP rate limiting**: In-memory LRU cache, max 3 submissions per IP per hour
4. **Server-side validation**: All required fields, max lengths, category enum check, Game URL format validation
### UX
- Success: Clear success message after submission (inline, not toast)
- Error: Inline validation errors per field; rate limit exceeded shows friendly message asking to try later
- Accessible: Proper `<label>` associations, ARIA attributes, keyboard navigable, focus management
- No CAPTCHA — timing + honeypot + rate limit is sufficient
### SEO
- `robots: { index: false, follow: true }` — not useful indexed
- Simple metadata: title "Contact & Report — DeckyVault"
### File Structure
```
app/contact/
page.tsx — Client component: contact form with category picker, validation, submission
lib/api/contact.ts — Elysia route: POST handler, Discord webhook, rate limit, validation
```
---
## 3. SEO & Sitemap Updates
### Sitemap Additions
Current sitemap includes: `/`, `/devices`, `/game/{id}` (all), `/devices/{slug}` (all).
**Add:**
| URL | Priority | Change Frequency |
|-----|----------|-----------------|
| `/games` | 0.7 | weekly |
| `/contact` | 0.3 | monthly |
| `/login` | 0.3 | monthly |
| `/signup` | 0.3 | monthly |
**Enhancement:** Add `images` property to game sitemap entries using `capsuleImage` field.
### Robots.txt Updates
Add explicit disallow rules:
```
Disallow: /admin
Disallow: /api
```
This is in addition to the per-page `robots` metadata that already exists on admin and search pages.
### Files to Modify
```
app/sitemap.ts — Add static pages, enhance game entries with images
app/robots.ts — Add /admin and /api disallow rules
```
---
## Edge Cases & Security
- **Games list with zero results**: Show friendly empty state with icon + message
- **Games list API failure**: Client-side error state with retry CTA
- **Contact form rate limit exceeded**: HTTP 429 response, inline message "Please try again in a few minutes"
- **Discord webhook down**: Server catches webhook POST failure, returns 500 with generic error message (no Discord implementation details leaked)
- **XSS in form fields**: All fields validated server-side; Discord embed content is structured (not raw HTML)
- **Games grid empty DB**: Server component renders empty state, no client-side fallback needed
- **Deep pagination**: Cap offset at 10000 (to prevent DB strain from bots scrolling endlessly); show "You've reached the end" message