Gaming Settings: What Broke, and What I'd Recommend to a Fellow Vibe Coder
I was on the couch, phone in hand, trying to figure out what settings I'd curated for Gears 5 on my main rig. I picked the game, picked the computer, left the resolution at 4K, left the frame rate at 120, and got: "No settings have been curated yet for this game, computer, and target." Which was wrong. I'd curated it. I remembered curating it. The app was lying to me, and it took about ten seconds of staring at the screen before I realized the app wasn't lying — it was just answering the exact question I'd asked, which happened to be the wrong question.
That one dropdown bug turned into the most useful lesson from an otherwise straightforward weekend project: a mobile-first Next.js app that tracks my game library, my four gaming machines, and the optimal graphics settings for each (game, computer, resolution, frame rate) combination. Single passcode gate, Postgres on Neon, deployed on Vercel. The kind of app that used to take a weekend of actual typing and now takes an afternoon of describing what you want. But "an afternoon of describing what you want" still produces real bugs, real infrastructure decisions, and — this is the part worth writing down — real opportunities to build the wrong interface for your agent without noticing.
Why This App Exists
I've got four machines I actually play games on: AI-NT-No-Problem (the same RTX 5090 SFF build that still dual boots to Windows when not running the homelab, a living-room machine, a travel Steam Deck, and a second 1440P Arc-powered box at a second house. Every one of them has a different target resolution and frame rate, and "optimal settings" means something different on an Arc B580 than it does on a 5090. There's no API for "give me the best graphics settings for this game on this GPU." So the app's first phase is just a well-organized place to write those down by hand — split into system settings, GPU-vendor software settings, and in-game settings, because those are three genuinely different layers that get muddled together in every settings guide I've ever read.
Getting the schema and the CRUD right was the easy part. The interesting part started once I asked an agent to actually populate the thing with real, researched settings for seven games across all four machines.
Lesson 1: Give Your Agent a Real Interface, Not a Browser
The first pass at populating 28 settings profiles went through the app's own HTML form, driven by Playwright — log in, fill the game dropdown, fill the computer dropdown, fill three textareas, submit, repeat 28 times. It worked. It also felt exactly as fragile as it sounds: slow, dependent on CSS selectors staying put, and impossible to reuse from anywhere except a headless browser.
So the next move was to build the interface I actually wanted from the
start: a small JSON API (/api/games, /api/computers,
/api/settings-profiles, plus a bulk-upsert endpoint) behind a bearer
token, and a companion MCP server that wraps it as six tools —
list_games, create_game, list_computers, get_settings_profiles,
upsert_settings_profile, bulk_upsert_settings_profiles. Now a future
agent session can read my library and write curated profiles directly,
instead of pretending to be a person clicking buttons.
The part I'd actually recommend, though, isn't the API — it's writing down
the process alongside it. I added a docs/CURATING_SETTINGS.md that
spells out exactly how to research and populate a batch of profiles: pull
real IDs first, confirm scope instead of guessing, spawn one research
subagent per game (not per profile, since settings vary by engine and GPU
vendor, not by machine), and verify claims like "does this game actually
support DLSS" instead of assuming every modern title has the full upscaler
buffet. Six months from now, a session with zero memory of this one can pick
up the exact same workflow. The API makes the work possible. The doc makes
it repeatable.
If you're building anything you'll ask an agent to touch more than once, build the interface before you build the tenth Playwright script.
Lesson 2: An Exact Match Is a Trap Disguised as a Feature
Here's the dropdown bug. The /recommend page let you pick any game, any
computer, any resolution, any frame rate — four independent dropdowns, all
populated from static option lists. Under the hood, that's a lookup keyed on
all four values together. Gears 5 on my main rig was curated at 4K @
240fps. I asked for 4K @ 120fps. Different key, zero rows, "no settings
curated." Technically correct. Completely unhelpful.
The fix wasn't a smarter lookup — it was admitting the dropdowns shouldn't have offered options that couldn't possibly resolve to anything:
const curatedResolutions = [...new Set(
candidateProfiles.map((p) => p.targetResolution)
)];
// resolution options are now filtered to what's actually curated
// for this game + computer, and FPS options cascade off of thatOnce a game and computer are picked, the resolution dropdown only shows resolutions with an actual profile, and the FPS dropdown only shows frame rates that exist for that resolution. Pick a pair with nothing curated yet, and you get the full list back with an honest "nothing here yet, add the first one" — instead of a plausible-looking option that was always going to dead-end.
This is a pattern I'd watch for in any vibe-coded app with a composite key and a form: a form that lets you construct queries the data can't answer is a bug generator, not a feature. It looks like flexibility. It's actually just an invitation to file a confusing bug report against yourself.
Lesson 3: Your Secrets Lie to You Locally
Two infrastructure surprises, both from the "this should just work" category.
First: Neon's Vercel integration doesn't create a variable named
DIRECT_URL, despite that being the name every Prisma+Neon tutorial uses.
It creates DATABASE_URL_UNPOOLED. Small naming mismatch, whole broken
build if you don't catch it.
Second, and weirder: every Neon-provided environment variable is marked
Sensitive in Vercel, which means vercel env pull silently returns an
empty string for all of them, even when you're logged in and linked
correctly. Not an error — an empty string, which fails in exactly the
confusing way you'd expect. The fix was to stop trying to run migrations
from a local shell against production at all, and instead chain
prisma migrate deploy directly into the Vercel build script, so
migrations run at the one moment the real values actually exist.
Sensitive secrets are write-only by design. If your workflow assumes you can always read back what you wrote, budget an afternoon to discover the one provider where that assumption is false.
Lesson 4: Beware Tools That Match Themselves
The best bug of the whole session, and the dumbest. I wanted to kill a stale local dev server before starting a fresh one, so I ran something like:
pkill -9 -f "next start"; pnpm dev -p 3314That process died instantly, no output, no server. Because pkill -f
matches against the entire command line of every running process — and
the shell invocation running my own pkill command had the literal text
"next start" sitting right there in its own argv. The kill command killed
itself before it ever got to the actual target. The fix was trivial once
diagnosed: kill by port (fuser -k -n tcp 3314) instead of by a text
pattern that might describe the hunter as well as the prey.
The runner-up: a "stale port" mystery that turned out to be next start
running with NODE_ENV=production, which sets Secure on the session
cookie — and browsers flatly refuse to store Secure cookies over plain
http://localhost. The login form appeared to work, then silently didn't
redirect, forever. Not a stale process. A perfectly correct security default
doing exactly what it should, in a context (local HTTP) where it was
guaranteed to look like a bug.
When a tool or a default is genuinely working correctly and still causing you pain, you'll waste more time blaming the wrong layer than the actual fix takes. Ask "is this thing behaving exactly as designed, in a context it wasn't designed for?" before you go spelunking for a real bug.
None of this required a big architectural rethink. It required noticing, several separate times, that the fast path — scrape the form, guess the env var name, kill by pattern, assume the dropdown is harmless — was the one quietly generating the debugging session. The slow path each time was maybe twenty extra minutes: build the API, read the actual Neon docs, kill by port, filter the dropdown. Twenty minutes now, or an evening later trying to figure out why a page that looks completely reasonable is lying to you.
That trade is basically the whole job now. The agent will happily build either version — the fast one or the correct one — as fast as you can describe it. The judgment about which one you're actually asking for is still entirely yours.
By the Numbers
- 7 games curated across 4 computers — 28 settings profiles
- 1 shared MCP server, 6 tools, 0 more Playwright scripts needed going forward
- 1 dropdown bug caused entirely by an exact-match lookup with no fallback
- 1
pkillcommand that killed itself - 2 environment-variable surprises (
DATABASE_URL_UNPOOLED, and Sensitive vars pulling empty) - 2 pull requests, both reviewed and merged the same day
What's the last bug you found that turned out to be something working exactly as intended?