尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Game recommendation website focusing on the highest quality of reccomendations
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗(The GitHub repo was renamed to match: KaiMonno/GameTaste → KaiMonno/Aboulomania. GitHub
redirects the old clone/fetch/push URL automatically, but if your local clone still points at
the old name, run git remote set-url origin [email protected]:KaiMonno/Aboulomania.git to update
it - the redirect isn't guaranteed to work forever.)
AI game recommender. See game-recommender-mvp-plan.md and game-recommender-architecture.md for the full plan and the reasoning behind every decision - both are kept current, phase by phase, as the single source of truth for why something works the way it does, not just what the code does.
Live in production at aboulomania.com. Frontend on Vercel, backend
on Railway (api.aboulomania.com), database on Neon, auth on Clerk's production instance - see
"Production deployment" below for how that's wired and how to change it.
Current state: Phases 1-6 are done, plus a minimal manual "Already played" exclusion button (folded into Phase 6) and the "platforms you own" piece of Phase 7 (see mvp-plan.md - PC specs/ emulator support/controller availability, Phase 7's other three sub-features, were checked against IGDB's real schema and found to have no structured data anywhere, so they're deferred rather than built on a guess). That's the full core loop (hard filters → soft-score ranking → diversity selection → live LLM "why you'll like it" blurbs) and the full profile system (Clerk accounts, saved preference defaults, wishlist, Steam library import via OpenID sign-in or a pasted profile URL, manual "Already played" marking, owned-platforms exclusion) - all working end to end, signed-in or anonymous, in production. Phase 9 (homepage bookshelf margins - owned games/wishlist visualized as book spines) is also done.
What's actually left: the rest of Phase 7 (deferred, not scheduled - no data exists to build
it on), Celery/cron scheduling (sync/enrich/backfill scripts still run by hand), a frontend
picker UI for similar_to_game_id (wired on the backend, no way to pick a game from the UI
yet), and replacing the bookshelf's placeholder book-icon art with real art (deliberately
deferred by design, see Bookshelf.tsx). Phase 8 (Backloggd import) is shelved, not just
unbuilt - see mvp-plan.md Phase 8: the only path left after ruling out automated sync was a
user-provided export to parse, and Backloggd doesn't ship one, so there's nothing to build
"manual import" against.
If you're picking this up fresh, read "Gotchas found the hard way" below before you start - every one of them cost real debugging time once already.
backend/ FastAPI + SQLAlchemy + Alembic + IGDB/HLTB/Claude clients + sync scripts
frontend/ Next.js filter form + results list
Prerequisites:
X | None type syntax, which older Pythons reject).
Check with python3 --version. If your default python3 is older (e.g. Anaconda ships
3.9), install a newer one and call it explicitly:
brew install [email protected], then use python3.12 belowsudo apt install python3.12 python3.12-venv (or your distro's equivalent)node --version)[email protected]:KaiMonno/Aboulomania.git if you'll pushSteps:
Clone and enter the repo:
git clone [email protected]:KaiMonno/Aboulomania.git
cd Aboulomania
Start Postgres + Redis:
docker compose up -d
This maps Postgres to host port 5434, not the default 5432 — deliberately, so it
doesn't collide with a system-wide Postgres install some machines already have running
on 5432. If docker compose up fails with a port-in-use error anyway, something else is
using 5434 or 6379 on your machine; change the host-side port in docker-compose.yml and
update backend/.env to match.
Backend:
cd backend
python3.12 -m venv .venv # or python3.11/python3.10 - whatever 3.10+ you have
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # fill in IGDB_CLIENT_ID/SECRET and ANTHROPIC_API_KEY (see below)
alembic upgrade head
uvicorn app.main:app --reload
API is up at http://localhost:8000 (docs at /docs).
macOS + python.org-installed Python: if any script later fails with
SSLCertVerificationError: unable to get local issuer certificate, that Python build has no CA bundle wired up. Fix once with:open "/Applications/Python 3.12/Install Certificates.command"(adjust the version in the path to whatever you installed). This isn't needed for Homebrew-installed Python.
Tests: cd backend && pytest runs everything under tests/. Most tests are pure unit
tests (no DB needed); tests/test_recommendations_integration.py exercises the real
scoring engine against the dev Postgres catalog and skips itself gracefully if that isn't
reachable/seeded rather than failing the whole run.
Frontend:
cd frontend
npm install
cp .env.example .env.local
npm run dev
App is up at http://localhost:3000.
Get your own API keys/accounts. None of these are shared between developers - each of you
registers your own, in your own backend/.env/frontend/.env.local (see each file's
.env.example for every var name):
Everything above — the async SQLAlchemy extra, the pinned howlongtobeatpy version that
actually works against HLTB's current site — is already correct in requirements.txt, so a
plain pip install -r requirements.txt on a fresh machine should not hit the errors that were
worked through while first building this (see git history / commit messages if curious).
Every one of these cost real debugging time once already - check this list before assuming something's broken.
.env for the whole process lifetime. get_settings() is
lru_cache'd, so editing backend/.env (a new key, a changed value) does nothing to an
already-running uvicorn process - --reload only reacts to .py file changes, not .env.
Symptom: a brand new CLERK_SECRET_KEY/STEAM_API_KEY you just set still 500s/401s as if it
were empty. Fix: actually kill and restart uvicorn, every time you touch .env. Bit this
project twice (Clerk, then Steam) before this line got written.npm run build while npm run dev is running against the same frontend/.
Both write to .next/ and a concurrent build corrupts the dev server's cache - symptom is a
cryptic Cannot find module './13.js' (or similar numbered-chunk) error on page load. Fix:
stop the dev server first, build, then rm -rf .next and restart npm run dev fresh. If you
hit the error without realizing why, the same fix (stop, rm -rf .next, restart) resolves it
regardless of cause.alembic upgrade head, every time. Pulling someone else's commit
that adds a column/table doesn't apply it to your local Postgres - git pull and alembic upgrade head are two separate steps. Run the latter after every pull that touches
backend/alembic/versions/.clerk-backend-api once pulled in a newer httpx as a side effect, which silently broke the
pinned anthropic SDK's internal client construction - pip install succeeded, the test
suite passed (it mocks the Claude call), and the backend ran fine; only a live call to
explain_candidates actually failed, and /recommendations is designed to swallow that
failure as best-effort (same as a real Claude outage), so every search silently returned
why_recommended: null with no visible error for an entire phase of work. If you add or
upgrade any package, don't just trust pip install succeeding or the test suite passing -
also do one live smoke test of anything that constructs an API client (Anthropic, Steam,
Clerk) end to end. requirements.txt pins should always match what's installed
( the specific package) - check this explicitly after any install that touches a
shared dependency like /, since pip resolving a transitive bump silently is
exactly how this one slipped through.Important, easy to miss: /recommendations only ever returns games from the hand-curated
list (the repo-root Game List file → curated_list_games table, see mvp-plan.md Phase 3.7) -
not whatever the broad IGDB sync pulls in. Those are two separate population paths. Step 1
below is the one that actually matters for getting real results; step 4 (broad sync) is useful
context/tuning but isn't what /recommendations reads from.
Get IGDB credentials, then populate the curated set - this one script does IGDB search +
HLTB match for every title in Game List in one pass (no separate HLTB step needed for these):
cd backend && source .venv/bin/activate
python -m app.scripts.sync_curated_list
Re-run this after any edit to the repo-root Game List file - it diffs current membership
against the file (adds newly-listed titles, removes delisted ones) rather than taking manual
add/remove commands.
Hit /recommendations from the frontend (or curl//docs) and see whether the
hard-filter + soft-score ranking feels right (numeric match_score only so far - no
why_recommended/why_not yet, that needs step 3). Tune WEIGHTS in
backend/app/services/scoring.py based on what you see.
Once scoring feels right, add your ANTHROPIC_API_KEY (see "Get your own API
keys/accounts" above) and run python -m app.scripts.enrich_games --limit 10 first - costs
real money per call, so spot-check a small batch's accuracy before scaling up to the full
curated set (drop --limit once you trust it; no args defaults to curated-only, same scope
as /recommendations itself). Populates the v3 enrichment fields both llm_enrichment.py
(this offline pass) and llm_explanations.py (the live, per-search blurbs, Phase 4) depend
on - once this has run, why_recommended/why_not start showing up on real requests.
(Optional) Broader catalog sync, for context/tuning or future re-curation - not required
for /recommendations, which never reads from outside the curated set:
python -m app.scripts.sync_igdb --pages 1 # 500 most-popular games into `games`
python -m app.scripts.match_hltb # HLTB length for those same games
Expect some HLTB misses — it has no official API (see mvp-plan.md §1/§5); check the logs for unmatched titles rather than assuming 100% coverage.
Accounts/wishlist/preferences (Phase 5), Steam import (Phase 6), and owned-platforms exclusion (Phase 7) are all done and need no further wiring - just the Clerk/Steam keys from "Get your own API keys/accounts" above. Once those are set ( - see Gotchas), sign in via the header, and:
Every developer's games/curated_list_games data has so far lived only in their own local
Postgres (see "Gotchas found the hard way" above). Ahead of deploying, that catalog - the
expensive, shared asset (IGDB + HLTB + LLM enrichment) - gets mirrored to a hosted Postgres
(Neon) so it isn't local-only. Per-user tables (users, wishlist_items, user_library_items,
user_preferences) are deliberately not part of this copy - a real deploy gets real
production user data, not anyone's local dev data.
Set up (already done once for this project - project icy-sound-26337237, branch production;
these steps are for re-running on a new machine or refreshing after a local enrichment run):
npm i -g neon@latest (or npx neon@latest in place of neon below), then from the repo
root: neon init -y --org-id <org-id> --project-id <project-id> --branch production --services postgres,
then neon login if it asks, then neon deploy. This pulls DATABASE_URL (pooled) and
DATABASE_URL_UNPOOLED (direct) into .env.local at the repo root - gitignored, bare
postgresql:// URLs with no SQLAlchemy dialect prefix.ssl=require, not libpq's
sslmode=require; psycopg2/Alembic should use the unpooled URL - DDL over a pgbouncer
pooler in transaction mode is the kind of thing that works until it doesn't):
postgresql+asyncpg://<user>:<pass>@<pooler-host>/<db>?ssl=require # app runtime
postgresql+psycopg2://<user>:<pass>@<direct-host>/<db>?sslmode=require # Alembic
Backend's .env keeps these as NEON_DATABASE_URL/NEON_DATABASE_URL_SYNC (see
.env.example) - NOT as the default DATABASE_URL/DATABASE_URL_SYNC, which still point at
local Postgres for day-to-day dev. Re-run neon env pull any time to refresh .env.local,
then redo this derivation.DATABASE_URL_SYNC="$NEON_DATABASE_URL_SYNC" alembic upgrade head
scripts/export_games_catalog.py):
python -m app.scripts.export_games_catalog "$NEON_DATABASE_URL"
DATABASE_URL at process start rather than editing .env's default:
DATABASE_URL="$NEON_DATABASE_URL" uvicorn app.main:app
The Neon project (icy-sound-26337237, org org-orange-forest-22978323) already exists -
don't create a new one. The project owner needs to invite the teammate first (Neon dashboard
→ org/project members - this has to be done by whoever owns the Neon account, it can't be
scripted from here). Once invited:
npm i -g neon@latest, then from the repo root:
neon init -y --org-id org-orange-forest-22978323 --project-id icy-sound-26337237 --branch production --services postgresneon login (their own Neon account - this opens a browser sign-in).neon env pull - pulls the same DATABASE_URL/DATABASE_URL_UNPOOLED into their own
gitignored .env.local. Derive NEON_DATABASE_URL/NEON_DATABASE_URL_SYNC from those the
same way (step 2 above) and add them to their own backend/.env.This does not change their day-to-day workflow - their local Postgres (docker-compose) is
still what DATABASE_URL/DATABASE_URL_SYNC point at by default, same as before. The Neon
access only matters when someone needs to refresh the hosted catalog mirror or test/deploy
against it.
Live at aboulomania.com. This is how it's actually wired - follow this to redeploy, rotate a key, or stand up a second environment (staging, a fork, etc.), not as a pending checklist.
frontend/ directory (Root Directory setting),
auto-deploys on push to main. Custom domain aboulomania.com + www.aboulomania.com
(apex redirects to www); env vars NEXT_PUBLIC_API_URL (the Railway backend URL below),
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY/CLERK_SECRET_KEY (Clerk's production, not
development, instance - pk_live_.../sk_live_...).backend/, also auto-deploys on push to main. Custom
domain api.aboulomania.com. Needs backend/.python-version pinned (see Gotchas) and these
env vars set to production values, not the local-dev defaults baked into config.py:
DATABASE_URL/DATABASE_URL_SYNC (the Neon URLs from "Hosted database" above),
CORS_ALLOWED_ORIGINS=https://aboulomania.com,https://www.aboulomania.com (additive to the
always-on localhost regex in main.py, doesn't affect local dev),
CLERK_AUTHORIZED_PARTIES (same two origins - see Gotchas for why both are required),
CLERK_SECRET_KEY/CLERK_JWT_KEY (Clerk's production instance, matching the frontend's
publishable key - mismatched dev/production keys between frontend and backend fail silently
as generic 401s, see Gotchas), and your own IGDB/ANTHROPIC/STEAM keys.clerk.aboulomania.com) verified in
Clerk's dashboard - required before sign-in works at all, see Gotchas.app/api/steam-openid/*:
the realm/return_to URLs are built from the live request's own origin at runtime, not hardcoded
or pre-registered with Steam, so it already works against whatever domain the frontend is on.backend/app/scripts/, runnable by hand or cron. Promote to Celery Beat only once you're
actually running these on a schedule and need retries/monitoring.pgvector/embeddings column — that's a v2 addition per the architecture doc, add it as a
new Alembic migration when you actually build similarity search.frontend/public/book-flip-sheet.png — "Pixel Book (Animated)" by Gokhan Solak
(opengameart.org/content/pixel-book-animated),
licensed CC-BY 3.0. Public attribution for this
lives in-app at /info (linked from the header), not just here — that's what actually satisfies
the license on the deployed site.frontend/public/library/book-*.png — "Tiny Library Assets" by Blubrry, sliced from a
TinyLibraryAssets.png sheet provided directly (no source URL or license terms given alongside
it) — credited by name at /info on that basis. Get the actual license from Blubrry before
relying on this for the live app the way the CC-BY-licensed book-flip art above already does.IGDB_CLIENT_IDSECRETsync_igdb.pyANTHROPIC_API_KEY): https://console.anthropic.com - needed for enrichment
and the live per-search explanations. Costs real money per call; see mvp-plan.md Phase 2/4
for measured per-call costs before running enrichment at scale.CLERK_SECRET_KEY + frontend's NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY/
CLERK_SECRET_KEY): https://clerk.com - free, create your own application, API Keys page.
Required for accounts/wishlist/Steam import/preferences - without it every /profile/* and
/wishlist/* endpoint 500s (fails closed, not open - see "Gotchas" below) and anonymous
search is all that works.STEAM_API_KEY): https://steamcommunity.com/dev/apikey - free, needed
for Steam library import. After you've populated games (see "What to do first" below -
this does nothing useful against an empty table), run
python -m app.scripts.backfill_igdb_fields once so those rows get a steam_appid to match
against (new rows get it automatically from then on via sync_igdb.py/enrich_games.py).pip freezehttpxpydantic.python-version, it picked a Python newer than psycopg2-binary/asyncpg
have prebuilt wheels for, so pip install fell back to compiling from source and failed on a
missing compiler/Postgres headers in the build image. Fixed by committing backend/.python-version
pinned to 3.12 (the newest version both packages ship wheels for) - if either gets bumped to a
version with wheels for something newer, this can follow, but don't just delete the pin.authenticate_request_async
returns a specific RequestState.reason (expired token, bad signature, clock skew, wrong
authorized party, JWKS fetch failure, ...) on every failure, but services/auth.py was
swallowing it into one generic "Not authenticated" - every production auth failure looked
identical regardless of cause. Cost real time ruling out a wrong Clerk application, a stale
CLERK_JWT_KEY, and a stale CLERK_SECRET_KEY one at a time, by hand, before adding
logger.warning("Clerk auth failed: reason=%s message=%s", ...) finally showed the real cause
in one Railway log line. If Clerk auth ever breaks again, check that log line first, not last.CLERK_AUTHORIZED_PARTIES has to match the exact origin the browser ends up on, not just
"the domain." aboulomania.com (apex) redirects to www.aboulomania.com - Clerk embeds
that post-redirect origin in the token's azp claim, so having only the apex (no www) in
CLERK_AUTHORIZED_PARTIES fails every authenticated request with TOKEN_INVALID_AUTHORIZED_PARTIES,
even though the domain "looks" configured. Set both: https://aboulomania.com,https://www.aboulomania.com.clerk.aboulomania.com)
that has to resolve via a CNAME from Clerk's dashboard - until that DNS record exists and
propagates, Clerk's JS never loads, so the Sign In button renders but clicking it does
nothing (no error, no console message pointing at DNS - just silence). If sign-in is inert in
production, check whether that subdomain resolves before anything else.backend/app/db.py) held onto a connection past
that point, so the next request to draw it hit asyncpg.exceptions.InterfaceError: connection is closed instead of a working query (seen live on /games/facets in production). Fixed with
pool_pre_ping=True on create_async_engine - tests each pooled connection before handing it
to a request and transparently replaces it if stale, without needing to know Neon's actual
timeout (which isn't published anywhere to tune a pool_recycle against).Cubes, FuzzyText, MagicBento, ReflectiveCard
in frontend/components/) are vendored as plain .jsx, trimmed from their original
reactbits.dev source to drop demo-only content and an unused @hugeicons/react dependency.
A few non-obvious things about them:
tsconfig.json's include glob, so tsc never type-checks the .jsx files
directly - but TypeScript still infers prop types from their untyped JS when a .tsx file
imports them (e.g. a prop with no default infers as required, a [] default infers as
never[]). MagicBento.d.ts/ReflectiveCard.d.ts exist specifically to override that
inference with real types; if you add a prop to the .jsx, update the matching .d.ts too.oklch(...) theme colors directly (glowColor,
tint, etc. expect hex/rgb) - getComputedStyle silently hands back the color unconverted
rather than converting it, which is a dead end. Convert empirically instead: a 1×1 canvas,
ctx.fillStyle = oklchString, then read it back with getImageData - this is how the
--primary/--accent hex values hardcoded at each call site were derived.ReflectiveCard opens its own full WebGL2 context per instance. Measured directly (not
assumed) in Chromium: the browser caps simultaneous WebGL2 contexts at 16 - past that,
the oldest instance silently loses its context (webglcontextlost, no crash, that card's
effect just stops rendering and falls back to a flat background). Matters most for the
wishlist grid specifically, since it's the one place these cards aren't capped at 5./wishlist./profile: import your Steam library (OpenID "Sign in through Steam" or a pasted
profile URL), manually mark individual games "Already played", and set which platforms you
own - all three exclude from future /recommendations results the same way.
Not yet: PC specs/emulator support/controller availability (the rest of Phase 7 - no
structured data exists for any of them, see mvp-plan.md) and Celery/cron scheduling — the
sync/match/enrich scripts are meant to be run by hand for now, then promoted to a scheduled
job once you trust the pipeline. Shelved: Phase 8 (Backloggd import) - see mvp-plan.md,
the only remaining path needed an export Backloggd doesn't ship.
then promoted to a scheduled job once you trust the pipeline.