feat: sitemap remediation, PWA activation, search refinements, Steam Deck UX

Sitemap:
- Switch from ISR (revalidate=3600) to force-dynamic for per-request generation
- Flatten into single app/sitemap.ts with inlined static entries and DB queries
- NULL-safe syncStatus filter: ne(games.syncStatus, 'failed') OR isNull(games.syncStatus)
- Structured JSON logging for generated URLs and DB errors
- Delete lib/sitemap/* helpers and app/api/revalidate-sitemap route
- Add basic vitest coverage for sitemap exports

PWA & Offline:
- Add @serwist/next service worker (webpack build) with runtime caching
- Cache strategies: stale-while-revalidate for game pages, network-first for listings/API,
  cache-first for Steam CDN images
- Offline fallback page (public/offline.html)
- Manifest icons: 192px maskable + 512px any
- Viewport meta with viewport-fit=cover, user-scalable=no
- Apple mobile web app meta tags

Search / Filter Refinements:
- Multi-genre OR support in listing API (comma-separated genres)
- Device-scoped FPS filter (min/max FPS constrained to selected device)
- Client-side URL state sync via router.replace for shareable filtered views
- Initialize filter state from URL params on mount
- Auto-collapse filter panel on saved-filter load
- Fix multi-genre saved filter parsing (comma-separated)

Steam Deck / Touch / Gamepad UX:
- WCAG 2.1 AA touch targets (44x44px) on all filter controls
- .gamepad-focus CSS focus ring for controller navigation
- useGamepadNavigation hook: D-pad/left-stick roving tabindex, A/B/X/Y actions
- Integrate gamepad hook into games page (X=search, Y=toggle filters)

Chore:
- Bump version to 2026.0.97
This commit is contained in:
2026-05-05 16:20:28 +08:00
parent 9be64359be
commit dcf6066b2b
25 changed files with 1396 additions and 489 deletions
-54
View File
@@ -1,54 +0,0 @@
import { revalidatePath } from "next/cache"
import { NextRequest, NextResponse } from "next/server"
/**
* On-demand sitemap revalidation webhook.
*
* What this endpoint does:
* Revalidates the `/sitemap.xml` path so Next.js regenerates the sitemap
* at the edge instead of waiting for the next ISR interval.
*
* When to call it:
* - After adding, updating, or removing games
* - After adding, updating, or removing hardware
* - After any bulk import or migration that affects public-facing URLs
*
* How to call it:
* ```bash
* curl -X POST https://<your-domain>/api/revalidate-sitemap \
* -H "Authorization: Bearer $REVALIDATE_SECRET"
* ```
*
* @see https://nextjs.org/docs/app/building-your-application/caching#on-demand-revalidation
*/
export async function POST(request: NextRequest): Promise<NextResponse> {
const authHeader = request.headers.get("authorization")
const secret = process.env.REVALIDATE_SECRET
if (!secret) {
return NextResponse.json(
{ error: "Revalidation not configured. Set REVALIDATE_SECRET env var." },
{ status: 503 },
)
}
const token = authHeader?.replace("Bearer ", "")
if (token !== secret) {
return NextResponse.json({ error: "Invalid secret" }, { status: 401 })
}
try {
revalidatePath("/sitemap.xml")
return NextResponse.json({
revalidated: true,
path: "/sitemap.xml",
now: Date.now(),
})
} catch (error) {
console.error("[Revalidate Sitemap] Failed:", error)
return NextResponse.json(
{ error: "Revalidation failed" },
{ status: 500 },
)
}
}