diff --git a/docs/superpowers/plans/2026-04-27-devices-admin.md b/docs/superpowers/plans/2026-04-27-devices-admin.md new file mode 100644 index 0000000..6389ab8 --- /dev/null +++ b/docs/superpowers/plans/2026-04-27-devices-admin.md @@ -0,0 +1,233 @@ +# Devices Redesign & Admin Panel — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Fix critical device page bugs, redesign devices list/detail pages with proper SEO, remove the broken Community Trust graph, and add an admin management panel with nested routes for Users, Hardware, and Games. + +**Architecture:** Server components fetch data and pass as props (true RSC pattern). Admin panel uses a `(admin)` route group with shared sidebar layout and nested routes. All devices code fixes the `"handled"` → `"handheld"` enum typo and the `upscalerBreakdown`/`fsrBreakdown` property mismatch. + +**Tech Stack:** Next.js 16 (App Router), React 19, Tailwind CSS v4, Drizzle ORM (PostgreSQL), better-auth (admin plugin), Elysia API, Framer Motion v12, ECharts, Lucide React icons + +--- + +### Task 1: DB Migration — Rename Enum & Add Image Column + +**Files:** +- Create: `drizzle/0011_rename-handled-to-handheld-add-image.sql` +- Modify: `lib/db/schema/hardware.ts` +- Modify: `lib/db/seed.ts` + +- [ ] **Step 1: Create the SQL migration file** + +Create `drizzle/0011_rename-handled-to-handheld-add-image.sql`: + +```sql +-- Rename pg_enum value from 'handled' to 'handheld' +ALTER TYPE device_type RENAME VALUE 'handled' TO 'handheld'; + +-- Add nullable image column to hardware table +ALTER TABLE hardware ADD COLUMN image text; +``` + +- [ ] **Step 2: Update the Drizzle schema to match** + +In `lib/db/schema/hardware.ts`, change the enum and add the image column: + +```ts +import { integer, pgEnum, pgTable, text, timestamp } from "drizzle-orm/pg-core" + +export const deviceTypeEnum = pgEnum("device_type", ["handheld", "console"]) + +export const hardware = pgTable("hardware", { + slug: text("slug").primaryKey(), + name: text("name").notNull(), + deviceType: deviceTypeEnum("device_type").notNull(), + image: text("image"), + sortOrder: integer("sort_order").default(0).notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), +}) +``` + +- [ ] **Step 3: Update the seed file** + +In `lib/db/seed.ts`, change all `"handled" as const` to `"handheld" as const`: + +```ts +const devices = [ + { + slug: "steamdeck-oled", + name: "Steam Deck OLED", + deviceType: "handheld" as const, + sortOrder: 0, + }, + { + slug: "steamdeck-lcd", + name: "Steam Deck LCD", + deviceType: "handheld" as const, + sortOrder: 1, + }, + { + slug: "steam-machine", + name: "Steam Machine", + deviceType: "console" as const, + sortOrder: 2, + }, +] +``` + +- [ ] **Step 4: Run the migration** + +Run: `cd /Users/adrianbonpin/Documents/Code/personal/deckyvault && bunx drizzle-kit push` +Expected: Migration applies — enum value renamed, image column added. + +- [ ] **Step 5: Run the seed to verify** + +Run: `cd /Users/adrianbonpin/Documents/Code/personal/deckyvault && bun run lib/db/seed.ts` +Expected: No conflict errors, devices seeded with `"handheld"` type. + +- [ ] **Step 6: Commit** + +```bash +git add drizzle/0011_rename-handled-to-handheld-add-image.sql lib/db/schema/hardware.ts lib/db/seed.ts +git commit -m "fix: rename device_type enum 'handled' to 'handheld', add image column" +``` + +--- + +### Task 2: Fix API Bug Fixes — avgFps Nulls & upscalerBreakdown Consistency + +**Files:** +- Modify: `lib/api/hardware-stats.ts` + +- [ ] **Step 1: Fix avgFps calculation to exclude null FPS entries** + +In `lib/api/hardware-stats.ts`, locate the `/:slug/stats` handler and replace the `avgFps` calculation: + +**Old code (around line ~90):** +```ts +const avgFps = Math.round( + (entries.reduce((s, e) => s + (e.fpsAvg ?? 0), 0) / totalBenchmarks) * 10 +) / 10 +``` + +**New code:** +```ts +// Filter out null FPS entries before computing average (SQL avg() ignores nulls) +const fpsEntries = entries.filter((e) => e.fpsAvg !== null) +const avgFps = + fpsEntries.length > 0 + ? Math.round( + (fpsEntries.reduce((s, e) => s + e.fpsAvg!, 0) / fpsEntries.length) * 10 + ) / 10 + : null +``` + +- [ ] **Step 2: Verify the API response shape uses `upscalerBreakdown` consistently** + +The API already returns `upscalerBreakdown` (this is the correct name). Confirm there is no `fsrBreakdown` in the API file. No changes needed here — the fix happens in the client component (Task 5). + +- [ ] **Step 3: Run the dev server and test the endpoint** + +Run: `cd /Users/adrianbonpin/Documents/Code/personal/deckyvault && curl -s http://localhost:3000/api/hardware/steamdeck-oled/stats | head -c 200` +Expected: JSON response with `avgFps` reflecting only non-null entries, and `upscalerBreakdown` (not `fsrBreakdown`). + +- [ ] **Step 4: Commit** + +```bash +git add lib/api/hardware-stats.ts +git commit -m "fix: exclude null FPS entries from avgFps calculation in device stats API" +``` + +--- + +### Task 3: Remove Community Trust Chart + +**Files:** +- Modify: `app/game/[id]/game-page-client.tsx` +- Modify: `lib/api/game-stats.ts` +- Delete: `components/charts/TrustBar.tsx` + +- [ ] **Step 1: Remove TrustBar from game-page-client.tsx** + +In `app/game/[id]/game-page-client.tsx`: + +1. **Remove the import** (line ~34): + Delete: `import { TrustBar } from "@/components/charts/TrustBar"` + +2. **Remove trust from the stats interface** (around line ~143): + Delete the entire `trust` array from the interface: + ```ts + // DELETE these lines: + trust: Array<{ + id: string + hardwareSlug: string + upvotes: number + downvotes: number + verifiedAt: string | null + userNotes: string | null + createdAt: string + }> + ``` + +3. **Remove trust from filteredStats** (around line ~326): + Delete the line: `trust: filterByDevice(stats.trust),` + +4. **Remove the Community Trust render section** (around line ~995): + Delete the entire block: + ```tsx + {/* Row 4 — Trust Bar */} + {filteredStats && filteredStats.trust.length > 0 && ( +
+

+ Community Trust +

+ +
+ )} + ``` + +- [ ] **Step 2: Remove trust data from game-stats API** + +In `lib/api/game-stats.ts`: + +1. **Remove Section 9** (trust data mapping, around line ~242): + Delete: + ```ts + // ── 9. Community trust ──────────────────────────────────────── + const trust = entries.map((e) => ({ + id: e.id, + hardwareSlug: e.hardwareSlug, + upvotes: e.upvotes, + downvotes: e.downvotes, + verifiedAt: e.verifiedAt ? e.verifiedAt.toISOString() : null, + userNotes: e.userNotes, + createdAt: e.createdAt.toISOString(), + })) + ``` + +2. **Remove `trust` from the empty return** (around line ~77): + Change: `trust: [],` → remove this line + +3. **Remove `trust` from the final return object** (around line ~276): + Delete the line: `trust,` + +- [ ] **Step 3: Delete the TrustBar component file** + +Delete: `components/charts/TrustBar.tsx` + +- [ ] **Step 4: Verify no other imports of TrustBar exist** + +Run: `cd /Users/adrianbonpin/Documents/Code/personal/deckyvault && grep -r "TrustBar" --include="*.tsx" --include="*.ts" app/ components/ lib/` +Expected: No results (TrustBar fully removed from codebase). + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "refactor: remove broken Community Trust chart (TrustBar) + +- Remove TrustBar component and chart +- Remove trust data from game-stats API response +- Remove trust section from game detail page +- DB columns and vote endpoints preserved for future voting system" +``` \ No newline at end of file