Skip to content

feat: library-based similar games from shared metadata - #3825

Draft
gantoine wants to merge 5 commits into
masterfrom
posthog-code/library-similar-games
Draft

feat: library-based similar games from shared metadata#3825
gantoine wants to merge 5 commits into
masterfrom
posthog-code/library-similar-games

Conversation

@gantoine

Copy link
Copy Markdown
Member

Description
Explain the changes or enhancements you are proposing with this pull request.

Replaces the IGDB similar_games list 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}/similar ranks other library ROMs by a weighted overlap of the normalized roms_metadata view signals:

Signal Weight
Franchise 5
Collection 4
Genre 3
Companies (dev/publisher) 2
Age ratings 1
  • Same-platform matches get a ×1.25 boost (cross-platform series matches still rank well), tiebroken by average rating.
  • The ROM itself, its siblings (same game, other dump/region), and anything hidden from the caller are excluded.
  • Strong signals (franchise/collection) are scored in full; broad signals (genre/company/age) are capped at 400 candidates so a large genre can't blow up the query. Uses the existing DB-agnostic json_array_contains_any helper (MariaDB/MySQL/Postgres).

Frontend (v2 only)

  • GameDetails.vue fetches similar games per-ROM (abort-on-navigate) instead of slicing igdb_metadata.similar_games.
  • New SimilarGamesGrid.vue renders real GameCards that link straight to /rom/{id} (no per-card IGDB lookup needed since every entry is owned).
  • Expansions / DLC / Remakes / Remasters are unchanged and still come from IGDB. v1 never displayed similar games, so it's untouched.

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.

  • I've tested the changes locally
  • I've updated relevant comments
  • I've assigned reviewers for this PR
  • I've added unit tests that cover the changes

Screenshots (if applicable)

N/A — verified via backend unit/endpoint tests, vue-tsc typecheck, production build, and trunk check (ruff/black/isort/mypy/eslint/prettier). A live browser pass was not run against this branch's stack.

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings July 19, 2026 13:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces IGDB suggestions with similar games from the local library. The main changes are:

  • Adds a metadata-overlap ranking query and API endpoint.
  • Excludes hidden, missing, sibling, and current ROMs.
  • Fetches results per ROM with navigation cancellation.
  • Renders owned results as standard game cards.
  • Adds endpoint and ranking tests.

Confidence Score: 4/5

The read-scope behavior needs to be fixed before merging.

  • The download-auth setting can remove authorization from the new metadata endpoint.
  • Reverse-only sibling relationships can appear in the recommendations.
  • The frontend request and rendering contracts are consistent with existing types.

backend/endpoints/roms/init.py and backend/handler/database/roms_handler.py

Security Review

The new read endpoint ties its scope requirement to the download-auth setting. Enabling unauthenticated downloads can therefore remove ROMS_READ authorization from a metadata endpoint.

Important Files Changed

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.

Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat: library-based similar games from s..." | Re-trigger Greptile

Comment thread backend/endpoints/roms/__init__.py
Comment thread backend/handler/database/roms_handler.py
@zurdi15

zurdi15 commented Jul 19, 2026

Copy link
Copy Markdown
Member

Love the idea!
I would like to keep the igdb similar games in the overview as a separated row tho, it's extremelly useful for me to discover new games

gantoine and others added 4 commits July 19, 2026 12:24
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>
@gantoine
gantoine marked this pull request as draft July 19, 2026 18:25
@gantoine gantoine added the on-hold Pending further research or blocked by another issue label Jul 19, 2026
@gantoine gantoine self-assigned this Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

on-hold Pending further research or blocked by another issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants