10254 Commits

Author SHA1 Message Date
Georges-Antoine Assi
a79c11965c Merge pull request #3608 from rommapp/claude/github-issue-3598-nu61ay
fix(v2): correct route name when navigating after platform/collection delete
2026-06-25 20:46:49 -04:00
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
Claude
302288e62f fix(v2): use correct route name when navigating after delete
Deleting a platform or collection in the v2 UI showed "Failed to
delete: unknown error" even though the deletion succeeded. The success
handler navigated to a non-existent route name ("platforms" /
"collections"), and Vue Router threw on the unknown name, which the
catch block surfaced as a generic error.

Navigate to the real index routes (PLATFORMS_INDEX / COLLECTIONS_INDEX)
instead.

Fixes #3598

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cv77VUJgPe1H7ipSRitjpH
2026-06-25 23:54:44 +00:00
Georges-Antoine Assi
009725e585 cleanup 2026-06-25 19:50:23 -04:00
Georges-Antoine Assi
cfff9f30e1 Merge pull request #3606 from rommapp/claude/github-issue-3590-tkdirn
fix(scan): prevent duplicate ROM entries from racing scans
2026-06-25 19:39:59 -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
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