Commit Graph

10246 Commits

Author SHA1 Message Date
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
Georges-Antoine Assi
4d8d85c0ff Merge pull request #3603 from rommapp/fix/cover-video-fit-and-dev-hmr
fix(frontend): contain hover-video to miximage frame + restore dev HMR
2026-06-25 18:15:33 -04:00
Georges-Antoine Assi
fb4f153fa0 cleanup comment 2026-06-25 17:17:23 -04:00
Georges-Antoine Assi
f6bc5264d5 fix(frontend): contain hover-video to miximage frame + restore dev HMR
The miximage hover video had no height bound, so a tall/narrow source
overflowed the cover. Match the miximage frame's box and `object-fit:
cover` it so the clip fills the bezel screen and crops instead of
spilling past it.

Also gate the PWA dev service worker behind DEV_PWA: it intercepted dev
requests and forced full page reloads on every edit (CSS included),
defeating HMR. Default dev now gets working HMR; set DEV_PWA=true to test
the PWA in dev.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:16:00 -04:00
Georges-Antoine Assi
a5e3150187 Merge pull request #3602 from rommapp/fix/webp-convert-on-startup
fix(webp): backfill cover conversion on startup when enabled
2026-06-25 16:09:47 -04: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
64b42d2206 Merge pull request #3597 from rommapp/fix/platform-update-422
fix(platform): accept embedded custom_name body on platform update
2026-06-25 10:01:49 -04:00
Zurdi
f5d5691f52 Merge pull request #3596 from rommapp/fix/light-theme
fix: Light theme
2026-06-25 15:33:30 +02: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
deb2c278bc Merge pull request #3595 from rommapp/perf/roms-list-query-optimizations
perf(roms): speed up the gallery/search list endpoint on large libraries
2026-06-25 09:18:42 -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
Zurdi
9daa8551a5 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-25 12:07:46 +02: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
ee978c62ca Merge pull request #3593 from y1lm-z/master
Turkish language support tr_TR
2026-06-24 21:06:08 -04:00
y1lm0z
6c79336c53 Turkish language support 2026-06-25 03:19:01 +03:00
Georges-Antoine Assi
167e355f3a Merge pull request #3592 from rommapp/claude/elegant-lovelace-nl78it
fix(deps): bump dependencies to patch reported vulnerabilities
2026-06-24 20:01:43 -04:00
Georges-Antoine Assi
81fc94c446 update lock 2026-06-24 19:55:12 -04:00
zurdi
579d81c6ed feat: added local dev script to gitignore 2026-06-24 20:31:41 +00:00
zurdi
99ea4f13c8 Refactor title shadow for light theme and update font family declarations 2026-06-24 17:55:06 +00:00
zurdi
c8bc51dc81 Enhance light theme support with color adjustments and new tokens 2026-06-24 17:52:21 +00:00
Claude
23dbffd17a fix(deps): bump dependencies to patch reported vulnerabilities
Address vulnerabilities flagged by the security scanner:

Backend (pyproject.toml / uv.lock):
- cryptography 46.0.5 -> 49.0.0 (CVE-2026-39892, memory buffer)
- mako 1.3.10 -> 1.3.12 (CVE-2026-41205/44307, path traversal)
- gunicorn 23.0.0 -> 26.0.0 (HTTP request smuggling hardening)
- yarl 1.20.1 -> 1.24.2 (SSRF via host parsing)

Frontend (package.json / package-lock.json):
- axios ^1.16.0 -> ^1.18.1 (sensitive data exposure)
- form-data pinned to ^4.0.6 via override (CVE-2026-12143, CRLF injection)

starlette is already on 1.0.1, which is the patched release for the
BadHost advisory (CVE-2026-48710), so no change is needed there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W5nnYmsCNdUjNpuwRxqUiR
2026-06-24 17:45:22 +00: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
40d949bcf0 Merge pull request #3588 from ametis70/github-actions-test-build-updates
Test Build GitHub Action improvements
2026-06-24 12:52:02 -04:00
Georges-Antoine Assi
93922faf91 Key Docker Hub namespace off push credential
Derive the Docker Hub namespace from DOCKER_NAMESPACE, falling back to
DOCKER_USERNAME and then github.repository_owner, so forks whose GitHub
owner name differs from their writable Docker Hub namespace can push.
GHCR is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:43:03 -04:00
Georges-Antoine Assi
da7658a4aa Clarify GHCR-only preview build PR comment
Rename the comment step to reflect that PR builds only push to GHCR,
add a note explaining the hardcoded registry, and inline the image
string into the updateComment call.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:26:30 -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
d96463846a Merge pull request #3585 from rommapp/add-test-data-generator
Add test data generator script
2026-06-23 22:16:06 -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
183894c180 Document backend/tools in repo-wide rules
Move the backend/tools note into the repo-wide rules section so it reads
as a standing convention rather than a backend command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 22:03:28 -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
40ad93c8b4 Merge branch 'master' into add-test-data-generator 2026-06-23 21:37:08 -04:00
Georges-Antoine Assi
0ae68ab560 Merge branch 'master' into claude/server-side-rom-patching-EaQck 2026-06-23 21:25:07 -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
fb266cf881 Merge pull request #3586 from rommapp/fix-vite-watch-resources-oom
Exclude served resources from Vite dev watcher
2026-06-23 21:05:39 -04:00
Georges-Antoine Assi
4fa46617a4 Exclude served resources from Vite dev watcher
frontend/assets/romm/resources is a symlink into the user's library
(covers, screenshots). Vite's root is frontend/, so chokidar follows the
symlink and tries to watch every file under it. With a large library
(hundreds of thousands of asset files) this exhausts the Node heap and
the dev server crashes with "JavaScript heap out of memory".

Ignore that tree in server.watch; it holds served assets that never need
HMR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:58:56 -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
80a968183d Move ROM patcher into a GameDetails tab, drop /patch route
Convert the standalone v2 Patcher view into a PatcherTab feature composite
rendered inside GameDetails (gated on the ROM carrying nested files), and
remove the dedicated rom/:rom/patch route along with every navigation
entry that led to it.

v2: remove the FilesTab button, the GameActions menu item, and
useGameActions.patch/canPatch; drop the route binding and named view.
v1 (frozen, removed per request): delete views/Patcher.vue, the AdminMenu
patcher item, and navigation store goPatcher.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:11:02 -04:00
Georges-Antoine Assi
ca7bd4c564 Add missing patcher i18n keys, ignore vendored patcher.js
Fill the 9 missing patcher.json keys across all non-English locales
(translated for fr/es/de/it/pt_BR, English placeholders elsewhere) and
exclude the vendored patcherjs library from trunk linting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 13:36:16 -04:00
Georges-Antoine Assi
3308a0a6a2 hold packed ratios 2026-06-23 11:36:25 -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