Files
deckyvault/docs/superpowers/specs/2026-04-27-consolidate-presets-design.md
T

91 lines
4.3 KiB
Markdown

# Consolidate Community Presets into Performance Entries
## Problem
The `community_presets` table is a separate entity that duplicates much of `performance_entries` (settingsJson, hardwareSlug, upvotes, timestamps) while requiring complex JOINs on the game page. There is no UI to create presets — the wizard only creates performance entries. Meanwhile, users cannot delete their own submissions.
## Design
### 1. Eliminate communityPresets — settingsJson ≠ null IS the preset
Any `performanceEntry` where `settingsJson IS NOT NULL` automatically appears as a "Community Preset" on the game page. No new column needed. The `settingsJson` column already exists on `performanceEntries` with the exact same type as `communityPresets.settingsJson`.
**Display logic**: Preset display names are auto-generated from entry data: `"{hardwareName} · {fpsAvg}fps"` with upscaler/frame-gen badges if present. No `name` or `description` columns added.
**Migration**: Drop `communityPresets` table entirely (no production data to preserve). No data migration needed.
### 2. User-owned hard delete
Users can permanently delete their own performance entries via `DELETE /api/performance/:id`. Authorization: `userId === session.user.id || role === "admin"`. This is a hard `DELETE FROM` — not a soft delete.
### 3. Unified voting
The existing `upvotes`/`downvotes` columns on `performanceEntries` replace the single `upvotes` column on `communityPresets`. Entries with settingsJson (presets) use the same upvote/downvote endpoints that already exist on the performance API.
## Affected Files
### Schema
- `lib/db/schema/communityPresets.ts` — DELETE entire file
- `lib/db/schema/index.ts` — Remove communityPresets re-export
- `lib/db/schema/performanceEntries.ts` — No changes (already has settingsJson, upvotes, downvotes)
### API Routes
- `lib/api/presets.ts` — DELETE entire file (CRUD, upvote, settings routes all removed)
- `lib/api/index.ts` — Remove preset route re-exports
- `lib/api/performance.ts` — Add user-scoped hard delete endpoint
- `lib/api/search-unified.ts` — Replace communityPresets count with filtered performanceEntries count
### Pages / UI
- `app/game/[id]/page.tsx` — Rewrite preset query: SELECT from performanceEntries WHERE settingsJson IS NOT NULL, JOIN gameVersions+hardware. Remove communityPresets imports/queries.
- `app/game/[id]/game-page-client.tsx` — Update Preset interface to match new data shape. Remove `name`/`description` fields. Auto-generate display names.
### Migration
- New Drizzle migration: DROP TABLE community_presets
## Query Changes
### Before (game page presets)
```sql
SELECT cp.*, pe.fps_avg, pe.fps_low, ...
FROM community_presets cp
LEFT JOIN performance_entries pe ON cp.performance_entry_id = pe.id
INNER JOIN hardware h ON cp.hardware_slug = h.slug
WHERE cp.game_id = $1
ORDER BY cp.upvotes DESC
```
### After (game page presets)
```sql
SELECT pe.id, pe.hardware_slug, h.name as hardware_name,
pe.fps_avg, pe.fps_low, pe.fps_high,
pe.upscaler_type, pe.upscaler_version, pe.frame_gen_method,
pe.proton_version, pe.os_version,
pe.settings_json, pe.upvotes, pe.created_at
FROM performance_entries pe
INNER JOIN game_versions gv ON pe.version_id = gv.id
INNER JOIN hardware h ON pe.hardware_slug = h.slug
WHERE gv.game_id = $1
AND pe.settings_json IS NOT NULL
AND pe.is_removed = false
ORDER BY pe.upvotes DESC
```
## Preset Display Name Generation
```typescript
function generatePresetName(preset: Preset): string {
const parts = [preset.hardwareName]
if (preset.fpsAvg !== null) parts.push(`${preset.fpsAvg}fps`)
return parts.join(' · ')
}
```
Upscaler and frame-gen are shown as badges next to the name, not in the name itself — matching the current game-page-client.tsx rendering.
## Edge Cases
- **Empty settingsJson array `[]`**: The wizard sends `settingsJson: settingsJson.length > 0 ? settingsJson : null`, so empty arrays are never stored. The query uses `IS NOT NULL` which is sufficient.
- **User tries to delete another user's entry**: 403 Forbidden unless admin.
- **Game page with no entries having settingsJson**: The presets section shows "No presets yet" — same as current empty state.
- **Upvoting presets**: Use the existing `POST /api/performance/:id/upvote` endpoint — works for all entries including presets.