A toolkit of micro-tools for indie game developers, built one tool at a time.
The first tool live is the Steam Capsule Grader: upload your capsule art and get a 0–100 score against a 10-point rubric derived from professional Steam capsule design practice, with a per-criterion breakdown, what's working, and specific fixes.
There is also a companion, free, open-source Claude skill pack for game design: https://github.com/rondorkerin/gamestack
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router), React 19 |
| Styling | Tailwind CSS v4 (CSS-first config, no tailwind.config.ts) |
| Auth | Supabase Auth via @supabase/ssr |
| Data | Supabase Postgres, accessed only with the service-role key from server code |
| Storage | Supabase Storage, private capsules bucket + short-lived signed URLs |
| AI | Provider-agnostic adapter layer; OpenAI vision models today |
| Hosting | Vercel (auto-deploy from main) |
| Package manager | pnpm workspaces |
apps/web Next.js app (the whole product surface)
packages/rubric The 10-criterion Steam capsule rubric + Steam asset specs
packages/ai Modular model-calling layer and provider adapters
supabase/migrations Database schema
NEXT16.md Next.js 16 house rules — read before writing app code
pnpm install
cp .env.example apps/web/.env.local # then fill in the values
pnpm devRequired environment variables are documented in .env.example. Without
OPENAI_API_KEY the app runs fine — the grader simply reports that no AI provider is
configured and hides the upload form, rather than failing at request time.
pnpm typecheck # tsc across every workspace package
pnpm build # production build
pnpm lint1. This is Next.js 16. middleware.ts does not exist here — it is src/proxy.ts
exporting a proxy function, always on the Node runtime. cookies(), headers(),
params and searchParams are all Promises you must await. See NEXT16.md.
2. There is no row-level security, on purpose. Access control lives in application
code, and every data read/write goes through the service-role client in server-only
modules. Because the tables therefore have no RLS to protect them, the migration
revokes all privileges from the anon and authenticated roles — otherwise the
public anon key could read every table straight off PostgREST. That REVOKE block is
the security boundary. Ownership checks (.eq('user_id', userId)) are the other half.
If you add a table, revoke it too.
New accounts get 50 credits. A grade costs credits derived from the tokens the model
actually spent — roughly 2 on the cheapest model, ~6 on the default, ~9 on the most
thorough, so 50 credits is about 8 grades on the default model or 25 on the fastest.
The rate lives in packages/ai/src/credits.ts; per-model pricing lives in
packages/ai/src/registry.ts and should be re-checked against the provider's pricing
page, since credit cost is derived from it.
Credits are only charged after a grade succeeds — a failed or rejected grade is free.
debit_credits is a single atomic Postgres function that decrements the balance and
writes the audit row in the same statement.
- Write an adapter satisfying
ProviderAdapterinpackages/ai/src/providers/. - Add one line to
ADAPTERSinpackages/ai/src/adapters.ts. - Add its models to
MODELSinpackages/ai/src/registry.ts.
Nothing outside packages/ai imports a provider SDK, and models whose provider has no
API key configured are automatically hidden from the picker.