3950 Commits

Author SHA1 Message Date
Georges-Antoine Assi
0af24b3fab Merge pull request #3607 from rommapp/claude/github-issue-3599-ye41l1
fix(scan): make scan tracker totals reflect platforms actually scanned
2026-06-25 20:40:22 -04:00
Georges-Antoine Assi
8405e574be fix(scan): reuse fetched platforms for NEW_PLATFORMS totals
Compute the "new platforms" totals from the platforms already loaded via
get_platforms() instead of issuing one get_platform_by_fs_slug query per
platform. Fix the test fixture to report the existing platform through
get_platforms() so the mocked data matches the code path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 19:59:52 -04:00
Georges-Antoine Assi
f818586b7d fix(scan): use get_platform_by_fs_slug for NEW_PLATFORMS totals
Compute the "new platforms" totals from the same existence check used
per-platform in _identify_platform, so the tracker totals match what is
actually scanned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 19:58:13 -04:00
Georges-Antoine Assi
009725e585 cleanup 2026-06-25 19:50:23 -04:00
Georges-Antoine Assi
ba6d0ef9db cleanup 2026-06-25 19:17:26 -04:00
Claude
e4660aca4c fix(scan): prevent duplicate ROM entries from racing scans
The patcher uploads the patched ROM and then fires a platform scan to
register it. When a second scan runs against the same platform around the
same time (a filesystem-watcher rescan, a scheduled rescan, or another
manual scan on a multi-worker setup), both scans could see the new file as
absent from the DB and each insert it, producing two identical library
entries for one patched file.

A platform folder can't physically hold two entries with the same name, so
a ROM is uniquely identified by (platform_id, fs_name). Enforce that with a
unique index instead of the previous plain index, which makes the duplicate
impossible. The scan's early ROM insert now adopts the row created by a
concurrent scan (catching the integrity error and skipping) instead of
failing, and ROM rename pre-checks for a name collision so it returns a
clean 409 rather than hitting the constraint.

Includes a migration that removes any pre-existing duplicates (keeping the
lowest id; dependents cascade) before upgrading the index to unique.

Fixes #3590

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135UV8Xn2XHkRhjzhm9UptP
2026-06-25 23:01:37 +00:00
Claude
fb6f6affad fix(scan): make scan tracker totals reflect platforms actually scanned
The scan tracker computed total_platforms and total_roms over every
filesystem platform, ignoring both the selected platforms and the scan
type. For a "new platforms" scan, existing platforms are skipped inside
_identify_platform, so their ROMs never count toward scanned_roms, yet
they were all included in total_roms. This made the tracker wildly
overcount (the whole library instead of just the new platform).

Resolve the platform list before computing totals and, for NEW_PLATFORMS
scans, exclude platforms that already exist in the database so the totals
match what is actually processed.

Fixes #3599

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158RJnc7MmAbwRz6Qiyrj5a
2026-06-25 22:53:46 +00:00
Georges-Antoine Assi
be8de5d0c3 cleanup comment 2026-06-25 16:09:15 -04:00
Georges-Antoine Assi
1ae49b6420 fix(webp): backfill cover conversion on startup when enabled
The frontend rewrites every cover URL to .webp as soon as the heartbeat
reports ENABLE_SCHEDULED_CONVERT_IMAGES_TO_WEBP, but existing covers have
no .webp sibling until the scheduled cron eventually runs (the inline
conversion only covers art fetched after enabling). This produced 404s on
all existing covers until the cron fired.

Enqueue a one-off backfill run of the conversion task on startup when the
feature is enabled, mirroring the recompute-save-hashes pattern. A fixed
job_id + Job.exists guard prevents duplicate jobs across restarts, and the
task already skips covers that have a .webp sibling so repeated runs are
cheap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:46:08 -04:00
Georges-Antoine Assi
0158097389 fix(platform): accept embedded custom_name body on platform update
Removing the aspect_ratio body field left update_platform with a single
scalar Body() param. FastAPI stops embedding a lone scalar body, so the
endpoint began expecting a bare JSON string while the frontend keeps
sending {"custom_name": "..."}, producing a 422 when editing a
platform's display name in v2.

Restore the embedded-key contract with Body(embed=True), matching the
frontend payload and every sibling update endpoint. Regenerate the
frontend types (restores the Body_update_platform model) and add an
endpoint regression test.

AI assistance: written with Claude Code (Opus 4.8).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:21:10 -04:00
Georges-Antoine Assi
6e3ef32815 fix(roms): gate sidecar caching on all active filters
The char index and rom id index sidecars are cached under a key that
encodes only user/order/grouping. is_unscoped previously excluded only
scope and search, so metadata/tag/status filters and the bool flags
applied to the query bypassed the gate: a filtered all-games request
stored a narrowed id list under the shared "all" key and later
unfiltered (or differently-filtered) requests read it back, showing the
wrong set and count of games.

Treat any narrowing parameter as scoped so those sets compute live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:10:58 -04:00
Georges-Antoine Assi
bd22a46eda refactor(roms): extract unscoped sidecar cache key into a helper
Deduplicate the identical cache key expression used by the char index,
filter values, and rom id index sidecars so the key scheme stays
consistent across them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:05:05 -04:00
Georges-Antoine Assi
67f3474d3f perf(roms): speed up the gallery/search list endpoint on large libraries
The /api/roms list endpoint did several O(library) computations on every
request. On a 100k-rom library each request took 4-5s. This addresses the
dominant costs, all measured on a real 100k-rom MariaDB.

- Cache rom_id_index: the full ordered id list backing virtual scroll was
  recomputed (the sibling-dedup window over the whole library) on every
  request, even limit=1, and shipped uncached. Memoise the unscoped scan
  under the same versioned cache as the other sidecars. 2815ms -> 7ms on hit.

- Slim the sibling-dedup query: the inner derived table materialized all of
  Rom (including JSON metadata blobs) for 100k rows, and carried a wide unused
  fs_name_no_ext through the window's temp table (spilling the sort to disk),
  plus a pointless inner ORDER BY. Select only the columns the window needs.
  2.79s -> 0.86s, identical results, no schema change.

- Rewrite with_char_index: replace row_number() over the whole library (full
  materialization + double filesort) with a per-letter COUNT and an
  accumulate. Identical output, drops a filesort layer.

- Add idx_roms_sibling_cover covering index for the sibling_roms view
  self-join, so the 7-way metadata-id OR resolves from the index instead of
  reading wide rows per parent. ~8x on dense pages warm, far more cold.

AI assistance: written with Claude Code (diagnosis, query rewrites, migration,
tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 22:29:09 -04:00
Georges-Antoine Assi
d0d2800fbc Merge pull request #3225 from rommapp/claude/server-side-rom-patching-EaQck
Add server-side ROM patching endpoint
2026-06-24 13:24:52 -04:00
Georges-Antoine Assi
462822cf81 Merge pull request #3587 from ametis70/pegasus-metafiles-export-fixes
Permission fix for exported game lists and Pegasus platform slugs mapping
2026-06-23 22:43:44 -04:00
Georges-Antoine Assi
b324ea5e37 fix typo 2026-06-23 22:38:43 -04:00
Georges-Antoine Assi
e6c3446c09 cleanup 2026-06-23 22:33:19 -04:00
Georges-Antoine Assi
55eb8b6252 Key SLUG_TO_PEGASUS by UniversalPlatformSlug
Use UPS members for the platform-slug keys instead of bare strings.
zxspectrum and windows now use their real UPS slugs (UPS.ZXS, UPS.WIN);
naomi, chip-8 and steam stay as raw strings since they have no UPS member
(platform.slug falls back to the folder name for those). Lookups by raw
slug string still resolve, since UPS is a StrEnum.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 22:28:57 -04:00
Georges-Antoine Assi
c87d880042 Standardize backend/tools file headers
Give all three tools the same header shape: shebang, summary docstring, a
short detail paragraph, and a "Run from the backend directory:" command
block. Adds docstrings to generate_supported_platforms.py and
xml_diagnostics.py, which previously had none.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 22:05:52 -04:00
Georges-Antoine Assi
a25ca20721 Move Python dev tools into backend/tools
Relocate generate_test_data.py (from backend/scripts) and
generate_supported_platforms.py (from backend/utils) into backend/tools,
alongside the existing xml_diagnostics.py. Update their run-command
references and document backend/tools in CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:57:34 -04:00
Georges-Antoine Assi
f5633f8dbe Don't echo seeded password in test data generator
CodeQL flags the value as clear-text logging of sensitive info. Print the
username and reference the --password flag instead of its value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:47:02 -04:00
Georges-Antoine Assi
18527c65a5 Silence bandit false positives in test data generator
- B311: random is used for deterministic fake data, not security
- B608: DELETE table names come from a hardcoded list, not user input

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:41:42 -04:00
Georges-Antoine Assi
c8055ac973 Address self-review on patcher PR
- patcher.js resolves rom-patcher-js from both the relocated sibling
  layout (docker/Dockerfile) and the plain node_modules layout (root
  Dockerfile), so both build flows work without a manual copy
- apply_patch wraps the node subprocess in asyncio.wait_for with a
  timeout and kills it on expiry; a semaphore bounds concurrency, and the
  endpoint rejects oversized ROM/patch files to avoid OOM
- report the patch source-checksum validation result via an
  X-Patch-Validated header; the patcher UI warns on a mismatch
- return a generic "Patching failed" detail to clients and log the real
  error server-side, so node/RomPatcher.js paths don't leak

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:07:27 -04:00
Georges-Antoine Assi
311d638e7d Generate mosaic PNG covers and screenshots
Add --images (default on) to write unique per-rom cover art and
screenshots into the resources tree, matching RomM's
roms/{platform_id}/{rom_id} layout, and point the path columns at them.

Each image is a small random block grid upscaled with NEAREST, which is
the fastest believable artwork (~0.3-0.6 ms/image) and compresses to
~1.3 KB. Pixels come from a separate RNG stream so rom rows stay
identical with or without --images. --no-images keeps the fast,
DB-only path; --resources-path overrides the output dir.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:58:39 -04:00
Georges-Antoine Assi
2ab0039d9c Fix circular import broken by isort reordering
isort sorted the adapters import ahead of the metadata package, which
defeated the import-cycle workaround. Guard the order with isort: off/on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 19:30:19 -04:00
Georges-Antoine Assi
6253b63fca Add test data generator script
Adds backend/scripts/generate_test_data.py, a tool that builds a large,
prod-like RomM library (platforms, users, devices, firmware, roms with
per-provider fake metadata, saves, states, screenshots, collections, sync
data, play sessions) and bulk-inserts it for load and UI testing.

Cover/manual/screenshot path and URL columns are left empty so no asset
files are referenced.

AI assistance: written with Claude Code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 19:20:22 -04:00
Georges-Antoine Assi
a3176494c9 Patcher backend refinements, dep cleanup, regenerated types
- Move default_category_for_non_nested validator onto RomFileSchema so
  top-level files default to category "game" (the v2 patcher's base-file
  filter relies on this).
- Use Annotated Body() in the patch endpoint; check patcher output via
  anyio async Path.
- Drop the now-unused client-side rom-patcher and vite-plugin-static-copy
  (patching is server-side); simplify the Storybook plugin filter.
- Regenerate frontend OpenAPI types.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:17:52 -04:00
Georges-Antoine Assi
52163633e5 Merge branch 'master' into claude/server-side-rom-patching-EaQck 2026-06-23 07:01:07 -04:00
Georges-Antoine Assi
e7dd013cfb Merge pull request #3580 from rommapp/claude/youthful-wozniak-ocjhc0
feat(v2): interactive 3D box art on the game detail hero
2026-06-22 21:37:02 -04:00
Georges-Antoine Assi
2631f0e45d Merge pull request #3577 from Spinnich/feature/oidc-allow-registration
feat(auth): add OIDC_ALLOW_REGISTRATION toggle to gate OIDC auto-registration
2026-06-22 18:06:13 -04:00
Georges-Antoine Assi
d38be187f3 Merge branch 'master' into claude/youthful-wozniak-ocjhc0 2026-06-22 18:04:37 -04:00
Georges-Antoine Assi
c4bfbac872 Merge branch 'master' into v2-ui-tweaks-gg 2026-06-22 17:45:59 -04:00
Claude
77980a79ec feat(v2): interactive 3D box art on game detail hero
Adds an RBox3D primitive that builds a rotatable, fake-3D game box from
three flat ScreenScraper scans (front, back, spine) using CSS 3D
transforms. Box proportions derive from the images themselves; it rotates
via pointer drag, arrow keys / gamepad D-pad, and the right analog stick,
drifts gently when idle, and honours prefers-reduced-motion.

The game detail hero (CoverColumn) upgrades to the spinning box when the
"3D box" boxart style is selected and the rom has the full set of faces,
falling back to the flat cover otherwise.

Backend: persist the box-2D-side (spine) scan locally, mirroring the
existing box-2D-back handling — new BOX2D_SIDE media type + box2d_side_path
on ss_metadata, opt-in via scan.media.

- RBox3D primitive + Storybook story (controls + keyboard-rotation play())
- useBoxFaces composable resolving the three faces + a `complete` gate
- box3d-alt i18n key across all locales
- backend BOX2D_SIDE persistence + tests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019itLXRfJXGGbhPY3JyqnuN
2026-06-22 21:45:39 +00:00
Georges-Antoine Assi
af4b057894 feat(v2): screenshot-forward covers + cover-art PIP for continue-playing & activity
Show a "where you left off" screenshot on the Home continue-playing rail and
the live-activity board, with a small cover-art thumbnail (PIP) in the corner
so the game stays identifiable. Both render at the image's natural aspect.

Backend:
- New shared util `continue_playing_screenshot(rom, latest_save)` resolving the
  image in priority order: latest save's screenshot, then title screen, then
  first gameplay screenshot (None → frontend falls back to cover art).
- `SimpleRomSchema.screenshot_path` populated only on the `last_played` query;
  `get_latest_saves_for_roms` batch handler (+ tests).
- ActivityEntry / ActivityEntrySchema gain `screenshot_path`, computed from the
  session player's latest save in both the socket and REST heartbeat paths.

Frontend:
- New shared `CoverArtPip.vue` (bottom-right 2D cover thumbnail), reused by
  GameCard and ActivityCard.
- Home continue-playing rail uses `screenshot_path` + PIP, natural aspect (no
  forced hero/style).
- Activity board: screenshot-forward cover + PIP, and a wrapping flex layout so
  cards share a uniform height with natural-ratio widths (gallery-card
  behavior).
- GameCover only keys the measured ratio by rom id for the rom's own cover, so
  a `coverSrc` override (screenshot) never pollutes the gallery's ratio cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:40:56 -04:00
Zurdi
b282a4cac7 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-22 18:03:51 +02:00
zurdi
8c20fd6547 feat(logging): enhance logging module with dynamic module name resolution and add module filtering options 2026-06-22 15:51:22 +00:00
Spinnich
11a81c756f feat(auth): add OIDC_ALLOW_REGISTRATION toggle
Gate automatic account creation on OIDC login behind a new OIDC_ALLOW_REGISTRATION environment variable. Defaults to true, preserving the current auto-provisioning behavior; set it to false to run OIDC in an "existing users only" mode, where a login from an email without an existing RomM account is rejected with a 403 instead of silently creating one. Existing users are unaffected either way.

Adds the config constant, env.template and docs entries, and tests covering the enabled/disabled and existing-user paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 12:33:00 +00:00
Georges-Antoine Assi
662dc89a24 Merge branch 'master' into aspect-ratio-no-no 2026-06-21 19:20:09 -04:00
Georges-Antoine Assi
1f98b2df14 run fmt 2026-06-21 17:47:06 -04:00
Georges-Antoine Assi
4724361f6d refactor: remove per-platform cover aspect-ratio setting
The platform aspect_ratio setting is dropped from the UI and the API
(platform update body + response schema) — nothing consumed it for
rendering, and covers now size to their image's natural aspect.

- SettingsTab: remove the cover-style / aspect-ratio picker (and its
  now-dead helpers, CSS, and unused imports); collapse to a single column.
- update_platform: drop the `aspect_ratio` body field; PlatformSchema no
  longer returns it; utils/platforms stops seeding the default.
- Regenerate the affected frontend types (PlatformSchema, update body).

The DB column stays (out of the update/response scope; dropping it would
be a separate destructive migration) but is no longer read or written
through the API.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:43:40 -04:00
Georges-Antoine Assi
389025942f changes from bot review 2026-06-21 17:42:25 -04:00
Georges-Antoine Assi
77970f6379 Merge branch 'master' into feature/device-flow-authorization 2026-06-21 16:25:39 -04:00
Zurdi
ee33bf1b71 Merge pull request #3569 from rommapp/feat/share-savestates
feat: share saves & states with other users
2026-06-21 21:18:34 +02:00
Ian Mancini
8e97a7f064 fix: add shortname dictionary for pegasus export 2026-06-21 16:17:56 -03:00
Ian Mancini
4e612d9a78 fix: chmod 600 -> 644 for exported files 2026-06-21 16:17:28 -03:00
Georges-Antoine Assi
0460133992 Secure activity identity, cut Redis churn, remove v1 activity
Backend:
- Resolve the acting user from the authenticated socket session on
  connect instead of trusting the client-supplied user_id, so a client
  can no longer spoof a "now playing" session for another user. Only
  rom_id/device_id come from the payload.
- Emit activity:update/clear through the already-initialised socket
  server instead of opening (and leaking) a fresh AsyncRedisManager per
  REST heartbeat.
- Collapse get_all_active's per-key GET into a single MGET.
- Drop the pure pass-through _build_activity_entry helper.

Frontend:
- Remove all activity emits from the v1 EmulatorJS Player; the v2 shell
  is the single driver of the activity lifecycle.
- Remove activity from the v1 UI entirely (Activity view, ActivityBtn,
  ActivePlayers on game details, navigation, and the now-v2-only route).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:12:18 -04:00
zurdi
9106c3721e feat: add visibility toggle for saves and states, enhance community sections
- Introduced new API endpoints for updating visibility of saves and states.
- Added `is_public` property to `SaveSchema` and `StateSchema`.
- Created new models for user saves and states with visibility attributes.
- Updated the `SaveDataTab` component to differentiate between "Mine" and "Community" sections.
- Implemented visibility toggle functionality for user saves and states.
- Enhanced localization files to include new strings for visibility actions.
2026-06-21 14:00:02 +00:00
nendo
812491f68c Refine device authorization flow
- device/init returns a relative verification_path; the client joins it
  with its own origin
- Render the v2 approval screen via the named v2 router outlet (was blank)
- DevicePair: RSpinner, keyboard-accessible scope chips, scrollable scopes;
  DevicePairShell uses always-light overlay tokens (no hex)
2026-06-21 10:57:48 +09:00
Georges-Antoine Assi
429e718a14 add ui for v2 2026-06-20 21:09:11 -04:00
Georges-Antoine Assi
4b091bfb34 Resolve activity device_type from the device record
Instead of always inferring "web" for browser-emitted activity events, look
the device up by device_id and use its client type, falling back to "web"
(the browser default) when no device record exists. Mirrors the REST
heartbeat endpoint, which derives device_type from device.client.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:56:03 -04:00