feat: library-based similar games from shared metadata - #3825
Draft
gantoine wants to merge 5 commits into
Draft
Conversation
Replace the IGDB `similar_games` list on the v2 game detail page with
"Similar games" computed from the local library. Similarity is a weighted
overlap of the normalized `RomMetadata` signals (franchises, collections,
genres, companies, age ratings) with a same-platform boost, so every entry
is a ROM the user owns and can open directly.
Backend:
- New `GET /roms/{id}/similar` endpoint returning `SimpleRomSchema[]`,
excluding the ROM itself, its siblings, and anything hidden from the
caller.
- `DBRomsHandler.get_similar_rom_ids()` scores candidates over the
`roms_metadata` view; strong signals (franchise/collection) are scored in
full, broad signals (genre/company/age) are capped to bound the query.
Frontend (v2 only):
- `GameDetails.vue` fetches similar games per-ROM (abort-on-navigate).
- New `SimilarGamesGrid.vue` renders real `GameCard`s linking to
`/rom/{id}`. Expansions/DLC/Remakes/Remasters still come from IGDB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Greptile SummaryThis PR replaces IGDB suggestions with similar games from the local library. The main changes are:
Confidence Score: 4/5The read-scope behavior needs to be fixed before merging.
backend/endpoints/roms/init.py and backend/handler/database/roms_handler.py
|
| Filename | Overview |
|---|---|
| backend/endpoints/roms/init.py | Adds the similar-ROM endpoint, but its read scope is incorrectly controlled by the download-auth setting. |
| backend/handler/database/roms_handler.py | Adds candidate selection and weighted ranking; sibling exclusion assumes relationships are stored in the target-to-sibling direction. |
| frontend/src/v2/views/GameDetails.vue | Adds abortable per-ROM loading and guards against stale responses after navigation. |
| frontend/src/v2/components/GameDetails/OverviewTab.vue | Changes the similar-games input from IGDB records to library ROMs. |
| frontend/src/v2/components/GameDetails/SimilarGamesGrid.vue | Adds a grid that renders similar library ROMs with GameCard. |
| frontend/src/services/api/rom.ts | Adds a typed, abortable client for the similar-ROM endpoint. |
| backend/tests/handler/database/test_similar_roms.py | Covers ranking, limits, metadata absence, hidden platforms, and forward sibling exclusion. |
| backend/tests/endpoints/roms/test_rom.py | Covers successful endpoint output and missing targets, but not scope behavior. |
Reviews (1): Last reviewed commit: "feat: library-based similar games from s..." | Re-trigger Greptile
Member
|
Love the idea! |
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
The sibling-exclusion query only read rows where the target is `SiblingRom.rom_id`, so a reverse-direction pairing could leave the other dump/region eligible as a similar game. Query both sides of the pairing instead of assuming the view stores symmetric rows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep the library-based "Similar games" list on top, and show IGDB's own similar_games below it as an external discovery row. The row drops any game already in the library (those surface in the metadata-based list above), so it only ever links out to games the user doesn't own. - New `IgdbSimilarGamesGrid.vue` resolves ownership via the IGDB -> RomM cross-reference and renders only unowned suggestions. - `RelatedGameCard` gains a `forceExternal` prop so the grid's cards skip the redundant per-card lookup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /roms/{id}/similar endpoint re-materialized the roms_metadata view
(a DB VIEW derived from per-provider JSON columns, unindexable) twice per
call, taking 5s+ on large libraries. The ranking is user-independent, so
cache it in Redis under the existing filter-values cache version (already
bumped on every scan / ROM write, so invalidation is free) and share it
across callers. Collapse the two candidate scans into one, floating
strong-signal matches to the top. Per-user visibility filtering and the
result limit move to the endpoint, which now hydrates only the final slice.
Frontend: fire the similar-games request in parallel with getRom on the
v2 detail page instead of chaining it behind currentRom, and differentiate
the owned ("Similar games") and IGDB ("Similar on IGDB") discovery rows.
AI assistance: written with Claude Code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Explain the changes or enhancements you are proposing with this pull request.
Replaces the IGDB
similar_gameslist on the v2 game detail page with a "Similar games" list computed from the local library. Instead of surfacing IGDB titles the user may not own (and cross-referencing each one), every entry is now a ROM already in the library, ranked by how much metadata it shares with the current game.How similarity is computed
A new endpoint
GET /roms/{id}/similarranks other library ROMs by a weighted overlap of the normalizedroms_metadataview signals:×1.25boost (cross-platform series matches still rank well), tiebroken by average rating.json_array_contains_anyhelper (MariaDB/MySQL/Postgres).Frontend (v2 only)
GameDetails.vuefetches similar games per-ROM (abort-on-navigate) instead of slicingigdb_metadata.similar_games.SimilarGamesGrid.vuerenders realGameCards that link straight to/rom/{id}(no per-card IGDB lookup needed since every entry is owned).Unidentified ROMs (or Hasheous-only ROMs with no normalized metadata) simply show no section, same graceful-empty behavior as before.
AI assistance disclosure
This change was implemented with AI assistance (Claude Code). AI wrote the backend endpoint/handler, the ranking logic, the frontend wiring, and the tests; all changes were reviewed by the author.
Checklist
Please check all that apply.
Screenshots (if applicable)
N/A — verified via backend unit/endpoint tests,
vue-tsctypecheck, production build, andtrunk check(ruff/black/isort/mypy/eslint/prettier). A live browser pass was not run against this branch's stack.🤖 Generated with Claude Code