RomM
Beautiful, powerful, self-hosted rom manager and player
Alternative to: antstream arcade
5.4.0-alpha.1
2026-10-06[!CAUTION] Give the first start time to finish its migrations. Several of this release’s migrations rewrite the
romstable and backfill it in batches, which can take a while on a large library. Don’t restart the container while they run.
[!WARNING] Cross-origin requests are denied by default. An unset or empty
ROMM_CORS_ALLOWED_ORIGINSused to allow every origin, and now allows none. If a browser-based client on another origin talks to your RomM, list its origin inROMM_CORS_ALLOWED_ORIGINS. A*still answers any origin, but without credentials, so a client that signs in with the session cookie needs its origin listed explicitly. #4699
[!WARNING] Add a reverse proxy on a public address to
FORWARDED_ALLOW_IPS. RomM now trustsX-Forwarded-Foronly from loopback and private ranges. A proxy outside those ranges has to be added, or every request is logged and rate limited as coming from the proxy’s address. #4748
Technical changes
🌎 ENVIRONMENT VARIABLES
Notifications and email
| variable | default | description |
|---|---|---|
| SMTP_HOST | - | SMTP server; email stays off until this and SMTP_FROM are set |
| SMTP_PORT | 587 | SMTP server port |
| SMTP_USERNAME | - | Login for the SMTP server, if it needs one |
| SMTP_PASSWORD | - | Password for the SMTP server |
| SMTP_FROM | - | Sender address, such as romm@example.com |
| SMTP_SECURITY | starttls | starttls, tls (implicit TLS, usually port 465) or none |
Audit log
| variable | default | description |
|---|---|---|
| AUDIT_LOG_RETENTION_DAYS | 90 | Days to keep audit events; 0 keeps them forever |
Library conversion
| variable | default | description |
|---|---|---|
| ROM_CONVERTO_ENABLED | false | Enable the “Convert library” task and ?format= download conversions |
| ROM_CONVERTO_TIMEOUT | 600 | Seconds per rom-converto operation |
| ROM_CONVERTO_MAX_CONCURRENCY | 2 | Concurrent conversions per web or task worker |
Devices and sync
| variable | default | description |
|---|---|---|
| DEVICE_INSTALL_ENABLED | true | Let users push a ROM to one of their devices for install |
| DEVICE_INSTALL_REQUEST_TTL_DAYS | 2 | Days an unfinished install request waits for its device; 0 waits forever |
| DEVICE_INSTALL_EXCLUDED_PLATFORM_SLUGS | win,win3x,win9x,windows-apps | Platforms that can never be pushed to a device |
| SYNC_RETROARCH_PSP_SERIAL_MAP | {} | JSON map of PSP serial to extensionless ROM file name, for RetroArch PSP saves whose title matches no ROM |
Scheduled cleanups
| variable | default | description |
|---|---|---|
| ENABLE_SCHEDULED_CLEANUP_NETPLAY | true | Clean up empty netplay rooms |
| SCHEDULED_CLEANUP_NETPLAY_CRON | */30 * * * * | When that cleanup runs |
| ENABLE_SCHEDULED_CLEANUP_UPLOAD_TMP | true | Clean up abandoned chunked uploads |
| SCHEDULED_CLEANUP_UPLOAD_TMP_CRON | 0 * * * * | When that cleanup runs |
| ENABLE_SCHEDULED_CLEANUP_ZIP_CACHE | true | Clean up stale cached ZIP files |
| SCHEDULED_CLEANUP_ZIP_CACHE_CRON | 0 4 * * * | When that cleanup runs |
| ENABLE_SCHEDULED_CLEANUP_SYNC_SESSIONS | true | Fail sync sessions no client completed |
| SCHEDULED_CLEANUP_SYNC_SESSIONS_CRON | 23 * * * * | When that cleanup runs |
General
| variable | default | description |
|---|---|---|
| ⚠️ ROMM_CORS_ALLOWED_ORIGINS | - | Empty now allows no cross-origin requests; a * allows any origin but never credentials |
| ⚠️ FORWARDED_ALLOW_IPS | local/loopback addresses | Proxies trusted to report the client’s address; was * |
| DISABLE_EASYRPG | false | Disable the EasyRPG player for everyone |
| SEVEN_ZIP_TIMEOUT | 180 | Timeout for 7-Zip operations in seconds, was 60 |
📡 API CHANGES
| Change | Description | | --- | --- | | ⚠️ CORS denied by default | `ROMM_CORS_ALLOWED_ORIGINS` now defaults to empty instead of `*`; browser clients on another origin must be allowlisted (#4699) | | CORS wildcard drops credentials | With `*` in `ROMM_CORS_ALLOWED_ORIGINS`, responses no longer send `Access-Control-Allow-Credentials` (#4699) | | Invalid credentials are unauthenticated | A malformed Basic header or an invalid/expired JWT bearer no longer 500s; the request proceeds unauthenticated and gets the route's 401/403 (#4799) | | ⚠️ `PermissionGroupSchema.is_system` removed | Replaced by `system_key` (`SystemGroupKey` or null) (#4767) | | Age limits on permissions | `PermissionGroupSchema/Create/Update` and `UserPermissionsSchema/Update` gain `age_limit`, `hide_unrated_roms`; updates apply them only with `set_age_settings: true` (#5081) | | Collection `rom_count` is computed | `rom_count` on collection, smart and virtual collection schemas is now read-only, derived from the visible `rom_ids` (#4930) | | ⚠️ Collection name errors | Duplicate name on create or rename returns 409 (was 500); names over 400 chars return 422 (#4997) | | ⚠️ `GET /api/netplay/list` | Scope `assets.read` - `roms.read`; `game_id` is now an integer ROM id, 404 for a missing or hidden ROM (#4700) | | ⚠️ Netplay socket rooms | `open-room` and password-less `join-room` require a signed-in user with `roms.read` who can see the ROM; the room password moved from `extra.room_password` to top-level `password` (#4700) | | ⚠️ `GET /api/play-sessions` | Without `devices.read`, only allowed when `device_id` equals the calling token's device; no longer defaults `device_id` from the token (#4956) | | ⚠️ `PlaySessionSchema.sync_session_id` removed | Field dropped from play session responses (#4670) | | Page params on play and sync sessions | `GET /api/play-sessions` and `GET /api/sync/sessions` use shared page params (`limit` 1 to 10000, `offset`); sync sessions gains `offset` (#4715) | | ⚠️ Sync `delete` operation | `SyncOperationSchema.action` adds `delete` (slot emptied on the server); `SyncNegotiateResponse` adds `total_delete` (#4674) | | `POST /api/sync/negotiate` payload | New `restore_unlisted` and `emulators` fields to re-offer unlisted saves and filter by emulator (#5091) | | Save sync baselines | `POST /api/saves` and `PUT /api/saves/{id}` take a `content_hash` query param; `POST /api/saves/{id}/downloaded` takes `content_hash` in the body (#4768) | | Save/state uploads validated | `emulator` must be a single folder name (max length enforced); file names over 255 bytes return 400; uploads to a hidden ROM return 404 (#4851, #4997) | | `SaveSchema` / `StateSchema` annotations | New `is_favorite` and `labels`; masked to defaults on another user's shared save or state (#4714) | | `StateSchema.core` | New field naming the RetroArch core that wrote the state (#4862) | | ⚠️ `PUT /api/roms/{id}` rename errors | Renaming onto an existing file returns 409 (was 500); an invalid or overlong `fs_name` returns 400 before any metadata fetch (#4997) | | `PUT /api/roms/{id}` manual metadata | `raw_manual_metadata` is validated, 422 when malformed; `ManualMetadata` gains `alternative_names` (#5111) | | `POST /api/roms/{id}/convert-to-folder` | Also returns 409 when an `.m3u` beside the file lists it; upload and walkthrough routes reject the same case (#5076) | | `GET /api/roms` relevance ordering | Empty `order_by` with a search term ranks by relevance on every database; `char_index` is empty under relevance order (#4996) | | `GET/HEAD /api/roms/{id}/content/{file_name}` | New `?format=` for converted single-file downloads (202 + `Retry-After` while converting, 406 when unavailable) (#4999) | | `GET /api/roms/{id}/content/{file_name}` | New `purpose=download\|play` query param, recorded in the audit log (#4748) | | `DetailedRomSchema.download_formats` | Formats the caller can request with `?format=` (#5000) | | `is_easyrpg_game` on ROM schemas | New boolean on `SimpleRomSchema` and `DetailedRomSchema` (#5077) | | Pinned media | `RomUserSchema.pinned_media` and `RomUserData.pinned_media` (ordered media keys, null for default) (#4764) | | ROM metadata fields | LaunchBox box art `box2d_*`/`box3d_*` (url and path), `video_path` now nullable (#5129); SS `physical_disc`/`physical_extra_discs` (#4885); SS and Hasheous `dump_regions/languages/tags` (#4740); RA `hash_match` (#4816) | | `PlatformSchema` | New computed `abbreviation` and `alternative_names` (#5107) | | `MusicTrackSchema.file_name` | New field (#5079) | | `DeviceSchema.capabilities` | New `capabilities` map on device schema and on `POST`/`PUT /api/devices` payloads (#4835) | | `GET /api/heartbeat` | Adds `EMULATION.DISABLE_EASYRPG` (#5077), `NOTIFICATIONS` (#4735), `DEVICE_INSTALL` (#4835), `CONVERTO` (#4999) | | `GET /api/config` | Adds `CONVERTO` and `CONVERTO_LIBRARY_TARGETS` (#4999) | | `POST /api/tasks/run/{task_name}` | Returns 409 when a single-instance task is already queued or running; `TaskInfo` adds `destructive` (#5000) | | `POST /api/reset-password` | Enforces the password policy with 400, leaving the reset link usable (#5106) | | OIDC callback | A provider error redirects to `/login?bypass_autologin=true` instead of 500 (#4746); an account linked to a different provider identity gets 403 (#4892) | | `POST /api/activity/heartbeat` | Returns 404 for a ROM hidden from the caller (#5117) | | ⚠️ `activity:update` socket event | Sent only to users who can see the ROM, instead of broadcast to every client (#5121) | | ⚠️ `scan` socket event payload | Validated strictly (`ScanPayload`, unknown keys rejected); invalid options, no scan worker, or a scan in flight emit `scan:done_ko` (#5014) | | `scan:scanning_rom` | Payload adds `is_new` (#4867) | | `sync:conflict` | Payload adds `rom_name` (#4552) | | Socket session revocation | Logging out or revoking a session disconnects the sockets it opened (#4732) | | Streaming session routes | Control routes take `container` and `claimed_at` query params; launch socket payloads add `claimed_at`, `core`, `core_tier`, import `refusals`; `StreamingContainerSchema` adds `supports_live_states`, `import_kinds`, `state_core`; 409 bodies use `ContainerBusyDetail` (#4631, #4691, #4796, #4859, #4862) |Notifications
| Method | Path | Description |
|---|---|---|
| GET | /api/notifications | List the caller’s notifications, newest first (#4732) |
| POST | /api/notifications | Send a notification to yourself, or to other users with users.write (#4732) |
| POST | /api/notifications/read | Mark the given notifications (or all) read (#4732) |
| DELETE | /api/notifications/{notification_id} | Dismiss one notification (#4732) |
| DELETE | /api/notifications | Dismiss all notifications (#4732) |
Notification channels
| Method | Path | Description |
|---|---|---|
| GET | /api/notification-channels | List the caller’s channels (Apprise, webhook, email) (#4735) |
| POST | /api/notification-channels | Create a channel; email addresses get a confirmation code (#4735) |
| PATCH | /api/notification-channels/{channel_id} | Update a channel (#4735) |
| DELETE | /api/notification-channels/{channel_id} | Delete a channel (#4735) |
| POST | /api/notification-channels/{channel_id}/test | Send a test notification now (#4735) |
| POST | /api/notification-channels/{channel_id}/confirm | Confirm an email channel with its code (#4735) |
| POST | /api/notification-channels/{channel_id}/resend-code | Resend the email confirmation code (#4735) |
| GET | /api/notification-channels/apprise-services | List Apprise services and their fields (admin) (#4769) |
| POST | /api/notification-channels/apprise-services/parse | Parse a service or Apprise URL into fields (admin) (#4769) |
Audit events
| Method | Path | Description |
|---|---|---|
| GET | /api/audit-events | Paginated audit log, filterable by actor, action, category, target, time range and search; admins with users.read see everyone (#4748) |
Device installs
| Method | Path | Description |
|---|---|---|
| GET | /api/devices/online | IDs of the caller’s devices with an open /devices socket (#4835) |
| POST | /api/devices/{device_id}/installs | Queue a ROM install on a device (#4835) |
| GET | /api/devices/{device_id}/installs | List the device’s pending and taken install requests (#4835) |
| POST | /api/devices/{device_id}/installs/claim | Device claims its pending requests (device-bound token only) (#4835) |
| PUT | /api/devices/{device_id}/installs/{request_id} | Device reports done, already_installed or failed (#4835) |
| DELETE | /api/devices/{device_id}/installs/{request_id} | Cancel a pending or taken request (#4835) |
| GET | /api/roms/{id}/installs | The caller’s open install requests for a ROM (#4835) |
RetroArch Cloud Sync (WebDAV)
| Method | Path | Description |
|---|---|---|
| OPTIONS | /api/sync/retroarch/{path} | Advertise DAV support (#3904) |
| PROPFIND | /api/sync/retroarch/{path} | Read-only browsing of roms/, saves/ and states/ (#3904) |
| GET, HEAD | /api/sync/retroarch/{path} | Serve the manifest or a save/state file (#3904) |
| PUT | /api/sync/retroarch/{path} | Upload a save/state, matched to a ROM by file name (#3904) |
| DELETE, MOVE | /api/sync/retroarch/{path} | Remove a save/state the client dropped (#3904) |
| MKCOL, LOCK, UNLOCK | /api/sync/retroarch/{path} | Accepted no-ops for WebDAV clients; uses HTTP Basic with a 401 challenge (#3904) |
Saves and states
| Method | Path | Description |
|---|---|---|
| PUT | /api/saves/{id}/favorite | Set is_favorite on a save (#4714) |
| PUT | /api/saves/{id}/labels | Replace a save’s labels (#4714) |
| PUT | /api/saves/{id}/file-name | Rename a save file and its screenshot (#4749) |
| PUT | /api/states/{id}/favorite | Set is_favorite on a state (#4714) |
| PUT | /api/states/{id}/labels | Replace a state’s labels (#4714) |
| PUT | /api/states/{id}/file-name | Rename a state file and its screenshot (#4749) |
Collections
| Method | Path | Description |
|---|---|---|
| PUT | /api/collections/{id}/visibility | Share or unshare a collection (owner only) (#4749) |
| PUT | /api/collections/smart/{id}/visibility | Share or unshare a smart collection (owner only) (#4749) |
ROMs, tasks and config
| Method | Path | Description |
|---|---|---|
| POST | /api/tasks/scan | Queue a library scan with a ScanPayload body; 202, 409 if one is in flight, 503 with no worker (#5014) |
| GET | /api/roms/{id}/easyrpg/{path} | Serve the EasyRPG index.json and game/RTP files for RPG Maker 2000/2003 games (#5077) |
| PUT | /api/config/converto_settings | Update download conversion settings (#4999) |
Socket.IO events
| Method | Path | Description |
|---|---|---|
| Server to client | notifications:new / notifications:read / notifications:dismissed | Notification created, marked read, or dismissed for the user (#4732) |
| Server to client | install:updated | An install request changed, sent to the owner’s tabs (#4835) |
| Server to client | install:queued / install:cancelled | Sent to a device on the new /devices namespace (device-bound client token required) (#4835) |
| Server to client | activity:refresh | Signals clients to refetch the activity list (#5125) |
Highlights
Notifications
RomM now keeps a notification inbox for each user. Scans and tasks that finish or fail, a role change, or someone ending your stream all leave a notification that is pushed to every open tab and still waits for you the next time you open RomM. Admins can send their own from the Send tab on the Notifications page, and any API client can post one through POST /api/notifications. #4732
Each user can also forward their notifications to channels, filtered by level and topic. A RomM webhook sends a JSON payload (signed with HMAC-SHA256 when you give it a secret), and email works once the SMTP_* variables are set, which also sends password reset links by email. Admins can additionally pick any of the services Apprise supports, such as Discord, Telegram, Slack, ntfy, Gotify or Matrix, and fill in that service’s fields or paste its URL. #4735 #4769
RetroArch Cloud Sync
RomM is now a Cloud Sync target for RetroArch. Point RetroArch’s Cloud Sync at https://<your-romm>/api/sync/retroarch/ (the trailing slash matters) and sign in with your RomM username and password (setup guide). Saves and states sync straight into your library, web player states show up in RetroArch’s numbered load slots, and RetroArch registers itself as one of your devices on its first sync. #3904
The config, thumbnails and system file categories sync too, stored per user. PSP save folders are bundled into one save per folder; if a folder’s title matches no ROM, map its serial with SYNC_RETROARCH_PSP_SERIAL_MAP. Generic WebDAV clients can browse the same path read-only. #3931
Browser saves sync, and installs on your devices
All five in-browser players (EmulatorJS, EasyRPG, js-dos, PICO-8 and Ruffle) now sync saves through device sync. Each browser profile registers as a device, named like “Firefox on macOS”, and negotiates its saves with the server when a game launches, so a save made in one browser is waiting in another and a conflicting copy is archived instead of overwritten. A new Devices page at /devices lets you rename your devices, turn sync off for one, or remove it. #5091
You can also send a game to a device from the web UI. “Install on device” in a game’s menu adds it to the download queue of the devices you pick, and the device downloads it the next time it’s online. Only devices whose app reports it accepts installs are listed. Turn the feature off with DEVICE_INSTALL_ENABLED=false, and keep platforms out of it with DEVICE_INSTALL_EXCLUDED_PLATFORM_SLUGS. #4835
Parental controls
Give a permission group an age limit, and its members only see games rated for that age or younger. Each user can replace their group’s limit, and a “Hide unrated games” switch hides games no rating covers. Admins are never limited. Set them in the group and user dialogs under Administration. #5081 #5083
A game’s age comes from the strictest of its IGDB, ScreenScraper, LaunchBox (ESRB) and Steam ratings, and a manual rating list replaces the providers’ ratings. The limit applies everywhere a game can show up: the gallery, search, collections, stats, the Jukebox and feeds. See Parental controls for the details.
Audit log
RomM now records who did what: downloads and player launches, play sessions, uploads and edits, collection changes, scans and tasks, and security events like sign-ins, failed sign-ins and permission changes. Admins read it in the new Events tab under Settings › Logs, filtered by user, category and date, with a search over names and IP addresses. Events are kept for 90 days, set by AUDIT_LOG_RETENTION_DAYS (0 keeps them forever). See Audit log for everything that’s recorded. #4748
Library conversion with rom-converto
Pick a storage format per platform, and the new “Convert library” task converts every matched game on it in place, so a game keeps its saves and collections. Only lossless conversions are offered, and because the task deletes the originals it asks you to type a confirmation first. Set ROM_CONVERTO_ENABLED=true and choose formats under the new Conversion settings page, or in config.yml (setup guide). #4999
With download_conversion_enabled on, the game page’s More menu offers “Download as” in any format the file can be converted to, and clients can ask for one with ?format= on the download URL. #5000
converto:
download_conversion_enabled: true
platform_formats:
psx: chd
ngc: rvz
RPG Maker 2000/2003 in the browser
RPG Maker 2000 and 2003 games on the rpg-maker platform now play in the browser on the EasyRPG web player. The free EasyRPG RTP ships with it, so games that rely on the RTP start, though some assets (mostly in battles) are missing from it. Games need to be extracted folders, since the web player can’t read archives. Disable it with DISABLE_EASYRPG. #5077
Alternative titles and relevance in search
Search now matches the alternative titles your metadata providers know about, including localized names, so a Japanese title finds the game listed under its English name and the other way around. You can add your own in the new “Alternative titles” field of the Edit dialog, such as “ACNH” or a fan translation’s name. Already matched IGDB and Hasheous games pick up their regional titles on their next metadata refresh. #5111
The Search page also orders results by relevance until you pick a sort, with exact title matches first. #4995 #4996
Minor changes
- feat: Offer the desktop shell’s emulator from the save/state play page by @sdornan in https://github.com/rommapp/romm/pull/4541
- feat(metadata): take region and language from the dump a hash matched by @sdornan in https://github.com/rommapp/romm/pull/4664
- feat(metadata): read a fan translation’s tag and target language by @sdornan in https://github.com/rommapp/romm/pull/4665
- feat(saves): favorite and label saves and states by @zurdi15 in https://github.com/rommapp/romm/pull/4714
- feat(v2): multi-select saves and states by @zurdi15 in https://github.com/rommapp/romm/pull/4725
- feat(metadata): tag a dump from the flags ScreenScraper raises on it by @sdornan in https://github.com/rommapp/romm/pull/4666
- feat(v2): wider state cards and one timestamp across save and state views by @zurdi15 in https://github.com/rommapp/romm/pull/4736
- feat(sync): surface save conflicts detected by negotiate by @sdornan in https://github.com/rommapp/romm/pull/4552
- feat: promote platforms that have games in PlatformSelect by @andest01 in https://github.com/rommapp/romm/pull/4599
- feat: rename saves and states, and one public/private look across v2 by @zurdi15 in https://github.com/rommapp/romm/pull/4749
- feat(v2): surface WebSocket transport fallback in BackendStatusBanner by @andest01 in https://github.com/rommapp/romm/pull/4645
- feat(streaming): reap abandoned streaming sessions every minute by @LoneAngelFayt in https://github.com/rommapp/romm/pull/4632
- feat(sync): tell a device when the save it holds was deleted here by @sdornan in https://github.com/rommapp/romm/pull/4674
- feat(streaming): resume from a foreign emulator’s save or state via declared import by @LoneAngelFayt in https://github.com/rommapp/romm/pull/4691
- feat(v2): pin any media to the game overview by @gantoine in https://github.com/rommapp/romm/pull/4764
- feat(streaming): name containers by their label, and harden import archives by @LoneAngelFayt in https://github.com/rommapp/romm/pull/4796
- feat(sync): record a per-device save baseline to prove what did not change by @sdornan in https://github.com/rommapp/romm/pull/4768
- feat(streaming): per-platform RetroArch core override by @LoneAngelFayt in https://github.com/rommapp/romm/pull/4859
- feat(streaming): RetroArch states carry the core that wrote them by @LoneAngelFayt in https://github.com/rommapp/romm/pull/4862
- feat(v2): add an opt-in setting to show the game logo in place of the title on the v2 game page by @sdornan in https://github.com/rommapp/romm/pull/4888
- feat(soundtrack): control playback through the Media Session API by @sdornan in https://github.com/rommapp/romm/pull/4904
- feat(v2): promote platforms with games in the scan picker by @andest01 in https://github.com/rommapp/romm/pull/4897
- feat(search): search the gallery by providers’ alternative titles by @sdornan in https://github.com/rommapp/romm/pull/4941
- feat: read play sessions across devices and page sync sessions by @beemines in https://github.com/rommapp/romm/pull/4956
- feat: make netplay and upload cleanup schedules configurable by @beemines in https://github.com/rommapp/romm/pull/4957
- feat(search): rank multi-word searches by phrase on PostgreSQL by @sdornan in https://github.com/rommapp/romm/pull/4977
- feat(input): let the D-pad move through and out of text fields by @gantoine in https://github.com/rommapp/romm/pull/4993
- feat(search): Search provider aliases through search_titles and drop generated_search_aliases by @sdornan in https://github.com/rommapp/romm/pull/5003
- feat(converto): read per-file title ids with rom-converto during scans by @gantoine in https://github.com/rommapp/romm/pull/5031
- feat(i18n): follow the browser’s language by default, with an “Auto” language option by @sdornan in https://github.com/rommapp/romm/pull/5051
- feat(api): Add a REST endpoint to start a library scan by @ilyas-hallak in https://github.com/rommapp/romm/pull/5014
- feat(soundtrack): play SPC, VGM, VGZ and GYM soundtracks in the browser by @sdornan in https://github.com/rommapp/romm/pull/5079
- feat(v2): add an
activetoggle state to RBtn by @sdornan in https://github.com/rommapp/romm/pull/5090 - feat(v2): resume jukebox music after a reload by @gantoine in https://github.com/rommapp/romm/pull/5095
- feat(v2): scope stored preferences to the signed-in user by @gantoine in https://github.com/rommapp/romm/pull/5098
- feat(launchbox): scrape box front, back, spine and 3D box art by @sdornan in https://github.com/rommapp/romm/pull/5129
- feat: search platforms by slug, abbreviation and alternative names by @sdornan in https://github.com/rommapp/romm/pull/5107
Fixes
- fix(cloud-sync): remap web-player states into RetroArch’s numbered load slots by @fmustafayaman in https://github.com/rommapp/romm/pull/3933
- fix(roms): order a playlist by disc number, not by file name by @sdornan in https://github.com/rommapp/romm/pull/4737
- fix(v2): run desktop list rows to the screen edges by @zurdi15 in https://github.com/rommapp/romm/pull/4739
- fix(roms): order lettered discs like numbered ones by @sdornan in https://github.com/rommapp/romm/pull/4738
- fix(scan): keep a hash source’s tags when a scan skips it by @zurdi15 in https://github.com/rommapp/romm/pull/4740
- fix(docker): stop the watchdog from spawning duplicate RQ workers by @zurdi15 in https://github.com/rommapp/romm/pull/4743
- fix(hltb): retry a separated title with the separator dropped by @sdornan in https://github.com/rommapp/romm/pull/4759
- fix(v2): handle copy to clipboard over plain HTTP in logs and files tab by @sdornan in https://github.com/rommapp/romm/pull/4757
- fix(hltb): match search results by their aliases too by @sdornan in https://github.com/rommapp/romm/pull/4760
- fix(hltb): accept token-only sessions and discover the endpoint when GitHub fails by @sdornan in https://github.com/rommapp/romm/pull/4758
- fix(v2): version ROM file URLs with the file’s own timestamp by @gantoine in https://github.com/rommapp/romm/pull/4762
- fix(auth): redirect instead of 500 when the OIDC callback is rejected by @andest01 in https://github.com/rommapp/romm/pull/4746
- fix(streaming): bind every session action to the claim that owns it by @LoneAngelFayt in https://github.com/rommapp/romm/pull/4631
- fix(sync): give a sync session the life of one launch, and drop the dead play-session link by @sdornan in https://github.com/rommapp/romm/pull/4670
- fix(steam): read appdetails envelope keyed by another app ID by @sdornan in https://github.com/rommapp/romm/pull/4774
- fix(scan): keep title-id settled file categories and report id-only changes on refresh by @tmgast in https://github.com/rommapp/romm/pull/4720
- fix(security): deny cross-origin requests by default by @sdornan in https://github.com/rommapp/romm/pull/4699
- fix(backend): detect top-level rom files at any folder depth by @sdornan in https://github.com/rommapp/romm/pull/4776
- fix(soundtrack): fall back to the default cover when a game has no artwork by @sdornan in https://github.com/rommapp/romm/pull/4779
- fix(security): authorize netplay rooms on ROM access by @sdornan in https://github.com/rommapp/romm/pull/4700
- fix(backend): replace the legacy Query API with Select and type the session decorators by @sdornan in https://github.com/rommapp/romm/pull/4730
- fix(frontend): only request platform icons that ship by @gantoine in https://github.com/rommapp/romm/pull/4792
- fix(player): make all Amiga Kickstarts available to PUAE by @yiannias in https://github.com/rommapp/romm/pull/4741
- fix(auth): treat invalid bearer and basic credentials as unauthenticated by @gantoine in https://github.com/rommapp/romm/pull/4799
- fix(auth): rename seeded permission groups to Viewer and Editor by @sdornan in https://github.com/rommapp/romm/pull/4767
- fix(sync): make deleted-save tracking exact, complete, and effective on filled slots by @sdornan in https://github.com/rommapp/romm/pull/4789
- fix(sync): follow-ups to deleted-save tracking by @gantoine in https://github.com/rommapp/romm/pull/4807
- fix(scan): compute RA hash for archives whose member read fails by @sdornan in https://github.com/rommapp/romm/pull/4806
- fix(player): assign a gamepad already connected when EmulatorJS boots by @gantoine in https://github.com/rommapp/romm/pull/4808
- fix(v2): drop the CRT overlay’s backdrop grade on phones by @gantoine in https://github.com/rommapp/romm/pull/4809
- fix(playmatch): skip unhashed lookups of folder ROM members by @gantoine in https://github.com/rommapp/romm/pull/4812
- fix(v2): skip the post-upload scan while a scan is already running by @gantoine in https://github.com/rommapp/romm/pull/4810
- fix(v2): keep the focused platform tile in view when moving up the grid by @gantoine in https://github.com/rommapp/romm/pull/4811
- fix(ra): count RomM’s own RA hash match as RetroAchievements verification by @sdornan in https://github.com/rommapp/romm/pull/4816
- fix(saves): stamp save and state file names with local time by @gantoine in https://github.com/rommapp/romm/pull/4815
- fix(ra): keep a hash match when RA’s details request fails by @sdornan in https://github.com/rommapp/romm/pull/4822
- fix(ra): accept null Developer, Publisher and Genre from RetroAchievements by @sdornan in https://github.com/rommapp/romm/pull/4840
- fix(hasheous): match multi-file roms when one file is unknown by @sdornan in https://github.com/rommapp/romm/pull/4846
- fix(backend): restart the log forwarder and harden three Redis call sites by @sdornan in https://github.com/rommapp/romm/pull/4847
- fix(v2): stop URL query writes from dropping history entries; bump vue-router to 5.3.1 by @sdornan in https://github.com/rommapp/romm/pull/4861
- fix(player): boot cue/gdi/ccd/mds disc sets whole by default by @gantoine in https://github.com/rommapp/romm/pull/4848
- fix(ss): map Win9x to ScreenScraper’s generic Windows system by @gantoine in https://github.com/rommapp/romm/pull/4849
- fix(assets): reject emulator names that are not a single folder on upload by @gantoine in https://github.com/rommapp/romm/pull/4851
- fix(v2): keep the checkbox’s native input inside its label by @gantoine in https://github.com/rommapp/romm/pull/4856
- fix(scan): raise the 7-Zip timeout default and keep partial bytes out of the fallback hash by @gantoine in https://github.com/rommapp/romm/pull/4852
- fix(v2): mount one PDF viewer at a time in the Media tab by @gantoine in https://github.com/rommapp/romm/pull/4853
- fix(scan): anchor rom file category to the folder below the rom root by @gantoine in https://github.com/rommapp/romm/pull/4854
- fix(v2): move focus by arrow key and D-pad between page regions by @gantoine in https://github.com/rommapp/romm/pull/4855
- fix(v2): show TheGamesDB logo on server stats coverage chips by @sdornan in https://github.com/rommapp/romm/pull/4866
- fix(scan): kill a stalled 7-Zip extractor at the timeout by @gantoine in https://github.com/rommapp/romm/pull/4868
- fix(scan): stop rescans inflating live platform game counts by @sdornan in https://github.com/rommapp/romm/pull/4867
- fix(platforms): stop labeling C128 as Commodore 64 in the platform picker by @gantoine in https://github.com/rommapp/romm/pull/4874
- fix(player): keep disabled EmulatorJS cheats out of the core by @gantoine in https://github.com/rommapp/romm/pull/4876
- fix(player): restart the core after loading a save it did not boot with by @gantoine in https://github.com/rommapp/romm/pull/4877
- fix(roms): pick grouped representatives among siblings the user can see by @gantoine in https://github.com/rommapp/romm/pull/4878
- fix(player): keep arcade BIOS archives whole on EmulatorJS 4.2.3 by @gantoine in https://github.com/rommapp/romm/pull/4879
- fix(tests): redact provider credentials when recording VCR cassettes by @sdornan in https://github.com/rommapp/romm/pull/4886
- fix(netplay): connect over websocket only by @gantoine in https://github.com/rommapp/romm/pull/4889
- fix(ss): keep ScreenScraper disc art for every disc of a multi-disc game by @sdornan in https://github.com/rommapp/romm/pull/4885
- fix(auth): trust OIDC_TLS_CACERTFILE alongside the default CAs by @gantoine in https://github.com/rommapp/romm/pull/4891
- fix(oidc): fetch claims missing from the ID token from the UserInfo endpoint by @gantoine in https://github.com/rommapp/romm/pull/4893
- fix(auth): match OIDC users by their sub claim so an email change still logs in by @gantoine in https://github.com/rommapp/romm/pull/4892
- fix(v2): pin trailing: false on native reprobe throttle by @sdornan in https://github.com/rommapp/romm/pull/4928
- fix(frontend-v2): show the gallery scrollbar and add a back-to-top button by @gantoine in https://github.com/rommapp/romm/pull/4894
- fix(v2): match RSelect stacked label spacing to text fields by @sdornan in https://github.com/rommapp/romm/pull/4942
- fix(scan): reuse unchanged empty files on quick scan by @sdornan in https://github.com/rommapp/romm/pull/4949
- fix: serialize single-file ROM promotion across gunicorn workers by @sdornan in https://github.com/rommapp/romm/pull/4898
- fix(streaming): let the Webstation frame use camera, microphone and clipboard by @LoneAngelFayt in https://github.com/rommapp/romm/pull/4923
- fix(auth): hash passwords with bcrypt directly and upgrade to bcrypt 5 by @sdornan in https://github.com/rommapp/romm/pull/4916
- fix(roms): list the plain filename first in Content-Disposition by @gantoine in https://github.com/rommapp/romm/pull/4939
- fix(backend): keep derived rom state in step with its source by @sdornan in https://github.com/rommapp/romm/pull/4931
- fix(ra): match RetroAchievements by RA hash only, never by Hasheous’ game ID by @sdornan in https://github.com/rommapp/romm/pull/4962
- fix(v2): hide the game Achievements tab when the ROM has no achievements by @sdornan in https://github.com/rommapp/romm/pull/4968
- fix(player): EmulatorJS multi-disk boot, Safari fullscreen quit, sparse gamepads, netplay audio, mame2003_plus default by @gantoine in https://github.com/rommapp/romm/pull/4953
- fix(a11y): dark mode typography is WCAG AA safe by @andest01 in https://github.com/rommapp/romm/pull/4955
- fix(tools): make generate_test_data work on PostgreSQL by @sdornan in https://github.com/rommapp/romm/pull/4978
- fix(tests): give each event loop its own fake async Redis client by @sdornan in https://github.com/rommapp/romm/pull/4979
- fix(streaming): let the desktop session own the controller by @LoneAngelFayt in https://github.com/rommapp/romm/pull/4974
- fix(v2): render the v2 pair page and share the auth footer by @gantoine in https://github.com/rommapp/romm/pull/4980
- fix(firmware): store uploads under the name written to disk by @LoneAngelFayt in https://github.com/rommapp/romm/pull/4986
- fix(v2): check the active sort in the gallery sort menu by @sdornan in https://github.com/rommapp/romm/pull/4998
- fix: eight bugs from hand-maintained lists drifting apart by @gantoine in https://github.com/rommapp/romm/pull/4991
- fix(v2): size gallery bulk actions for whole-result selections by @gantoine in https://github.com/rommapp/romm/pull/4896
- fix(api): return 4xx for overlong names and duplicate collections by @LoneAngelFayt in https://github.com/rommapp/romm/pull/4997
- fix(converto): refuse cue sheets that reach outside their folder by @gantoine in https://github.com/rommapp/romm/pull/5008
- fix(player): fix EmulatorJS keyboard-lock listener leak; use VueUse for v2 listener pairs by @sdornan in https://github.com/rommapp/romm/pull/5012
- fix(v2): hide match rename toggle when the file name would not change by @sdornan in https://github.com/rommapp/romm/pull/5016
- fix(backend): disable redis-py maintenance notifications probe by @sdornan in https://github.com/rommapp/romm/pull/5015
- fix(player): let the browser Back button leave a running player by @sdornan in https://github.com/rommapp/romm/pull/5020
- fix(v2): fix useResponsiveColumns observer leak; use VueUse for v2 resize observers by @sdornan in https://github.com/rommapp/romm/pull/5019
- fix(v2): say what a native launch reports when its session expired or a save was deleted by @sdornan in https://github.com/rommapp/romm/pull/5021
- fix(v2): enlarge region and language flags on v2 game cards by @gantoine in https://github.com/rommapp/romm/pull/5022
- fix(docker): print the configured ROMM_PORT in the startup log by @gantoine in https://github.com/rommapp/romm/pull/5023
- fix(streaming): release a failed claim from one place by @gantoine in https://github.com/rommapp/romm/pull/5024
- fix(v2): copy over plain HTTP and skip task polling in hidden tabs by @sdornan in https://github.com/rommapp/romm/pull/5040
- fix(v2): size the re-sort skeleton to the known result count by @sdornan in https://github.com/rommapp/romm/pull/5042
- fix(scan): refresh screenshots when only their order changes by @gantoine in https://github.com/rommapp/romm/pull/5029
- fix(scan): keep an uploaded manual when the rom had no scraped one by @gantoine in https://github.com/rommapp/romm/pull/5030
- fix(input): show the focus ring when arrow keys move focus in Firefox by @andest01 in https://github.com/rommapp/romm/pull/5066
- fix(db): keep iterated query results away from the cyclic GC by @LoneAngelFayt in https://github.com/rommapp/romm/pull/5068
- fix(v2): draw under the iOS status bar and respect safe-area insets by @sdornan in https://github.com/rommapp/romm/pull/5075
- fix(rom): keep a disc an .m3u lists in place when promoting to a folder by @sdornan in https://github.com/rommapp/romm/pull/5076
- fix(v2): unsubscribe from the emitter on unmount via useEmitterEvent by @sdornan in https://github.com/rommapp/romm/pull/5085
- fix(v2): input follow-ups: phantom-pad right stick, LB/RB on gallery routes by @sdornan in https://github.com/rommapp/romm/pull/5086
- fix(v2): validation, spinner, nested button and label bugs by @sdornan in https://github.com/rommapp/romm/pull/5088
- fix(i18n): correct es_ES “Platform updated” string by @sdornan in https://github.com/rommapp/romm/pull/5089
- fix(v2): draw region flags on Chromium/Windows and center them in card chips by @sdornan in https://github.com/rommapp/romm/pull/5101
- fix(auth): validate reset passwords and make the last-admin guard atomic, with tests by @sdornan in https://github.com/rommapp/romm/pull/5106
- fix(v2): vertically align icons in Playable and LIVE chips by @sdornan in https://github.com/rommapp/romm/pull/5109
- fix(v2): don’t re-roll header stats when switching platform tabs by @sdornan in https://github.com/rommapp/romm/pull/5110
- fix(sync): use only the device’s own SSH key; test push-pull sync and the folder watcher by @sdornan in https://github.com/rommapp/romm/pull/5108
- fix(metadata): one UTC helper for release dates, a lint rule against local-time parsing, and Flashpoint tests by @sdornan in https://github.com/rommapp/romm/pull/5114
- fix(sync, activity): folder scan crash and hidden-ROM heartbeat; test both by @sdornan in https://github.com/rommapp/romm/pull/5117
- fix(patcher): read the patcher’s report from its last JSON line; test the patch endpoint by @sdornan in https://github.com/rommapp/romm/pull/5120
- fix(activity): send play activity only to users who can see the ROM by @sdornan in https://github.com/rommapp/romm/pull/5121
- fix(metadata): five provider fixes; test RetroAchievements, SteamGridDB, MobyGames, Playmatch and Pouët by @sdornan in https://github.com/rommapp/romm/pull/5123
- fix(feeds): keep Switch files with uppercase extensions in the Tinfoil feed by @NotAFlightRisk in https://github.com/rommapp/romm/pull/5115
- fix(activity): coalesce activity refreshes and keep one list request in flight by @sdornan in https://github.com/rommapp/romm/pull/5125
- fix(metadata): five provider fixes; test UPC, LaunchBox media, Demozoo, ScreenScraper and HLTB by @sdornan in https://github.com/rommapp/romm/pull/5126
- fix(metadata): four provider fixes; test Hasheous, TGDB, CSDb, gamelist and libretro by @sdornan in https://github.com/rommapp/romm/pull/5128
- fix(igdb): harden Twitch auth; test the IGDB client and handler by @sdornan in https://github.com/rommapp/romm/pull/5133
- fix(sync): store RetroArch Cloud Sync blobs under the assets volume by @gantoine in #5155
- fix(v2): name dialogs and select listboxes; axe-check story overlays by @sdornan in #5151
Other changes
- chore(deps): update mariadb docker tag to v13 by @renovate[bot] in https://github.com/rommapp/romm/pull/4537
- test(backend): catch column nullability drift between models and migrations by @sdornan in https://github.com/rommapp/romm/pull/4756
- perf(backend): read the gallery sorts out of an index by @sdornan in https://github.com/rommapp/romm/pull/4647
- perf(sync): batch RetroArch manifest hashing and list PROPFIND paths unhashed by @gantoine in https://github.com/rommapp/romm/pull/4761
- build(deps): bump uvicorn to 0.53 and uvicorn-worker to 0.4 by @sdornan in https://github.com/rommapp/romm/pull/4729
- chore(deps): upgrade vitest and @vitest/ui to v5 by @sdornan in https://github.com/rommapp/romm/pull/4747
- chore(deps): bump fastapi to 0.141.1 and replace fastapi-pagination with a local page model by @sdornan in https://github.com/rommapp/romm/pull/4715
- docs(claude): trim root CLAUDE.md and split stack guidance into nested files by @gantoine in https://github.com/rommapp/romm/pull/4790
- ci: run mypy in its own workflow instead of through Trunk by @sdornan in https://github.com/rommapp/romm/pull/4724
- chore(mypy): require coded ignores and flag stale ones, type protected_route by @sdornan in https://github.com/rommapp/romm/pull/4794
- ci: nightly image, index annotations and SBOMs by @sdornan in https://github.com/rommapp/romm/pull/4731
- chore: delegate llm tasks to static analysis tools. by @andest01 in https://github.com/rommapp/romm/pull/4742
- chore(docker): verify downloads with ADD —checksum and pin RAHasher by commit by @sdornan in https://github.com/rommapp/romm/pull/4781
- refactor(backend): move engine-specific query SQL out of handlers into utils/sql_dialect.py by @sdornan in https://github.com/rommapp/romm/pull/4793
- chore(mypy): require full annotations outside tests, type the setup responses by @sdornan in https://github.com/rommapp/romm/pull/4795
- fix(deps): bump mariadb connector to 1.1.14 for Python 3.14 by @gantoine in https://github.com/rommapp/romm/pull/4814
- chore(mypy): switch to strict mode with listed exceptions by @sdornan in https://github.com/rommapp/romm/pull/4817
- chore(mypy): give every generic its type arguments by @sdornan in https://github.com/rommapp/romm/pull/4818
- test(sync): isolate RetroArch blob and PSP pending dirs per test by @sdornan in https://github.com/rommapp/romm/pull/4824
- refactor(backend): move FORCE INDEX hint and FULLTEXT match into sql_dialect by @sdornan in https://github.com/rommapp/romm/pull/4825
- chore(mypy): drop warn_return_any and validate provider responses with strict JSON by @sdornan in https://github.com/rommapp/romm/pull/4826
- chore(backend): enforce mypy’s disallow_untyped_calls and disallow_untyped_decorators by @sdornan in https://github.com/rommapp/romm/pull/4827
- chore(mypy): report untyped imports instead of disabling the check by @sdornan in https://github.com/rommapp/romm/pull/4828
- chore(deps): upgrade Authlib to 1.8 and joserfc to 1.7 by @sdornan in https://github.com/rommapp/romm/pull/4829
- chore(deps): move from httpx to httpx2 by @sdornan in https://github.com/rommapp/romm/pull/4830
- chore(backend): replace zstandard with stdlib compression.zstd by @sdornan in https://github.com/rommapp/romm/pull/4831
- chore(deps): bump uv to 0.12.19 and relax required-version for Dependabot by @sdornan in https://github.com/rommapp/romm/pull/4832
- fix(deps): bump asyncssh, click, pygments and requests for open advisories by @sdornan in https://github.com/rommapp/romm/pull/4833
- perf(gamelist): list each ES-DE media folder once per platform by @sdornan in https://github.com/rommapp/romm/pull/4841
- chore(deps): bump redis to 8.1 and fakeredis to 2.38, drop types-redis by @sdornan in https://github.com/rommapp/romm/pull/4844
- perf(scan): download a rom’s media files concurrently by @sdornan in https://github.com/rommapp/romm/pull/4842
- chore(skills): vendor humanizer and run it after pr-ready and the release drafts by @gantoine in https://github.com/rommapp/romm/pull/4845
- chore(mypy): replace type-ignore defaults with typed injection sentinels by @sdornan in https://github.com/rommapp/romm/pull/4843
- perf(backend): pipeline multi-command Redis writes by @sdornan in https://github.com/rommapp/romm/pull/4858
- chore(lint): ban double-cast ROM fixtures in tests by @gantoine in https://github.com/rommapp/romm/pull/4863
- docs: consolidate agent instructions into AGENTS.md and add a shared dev environment setup by @sdornan in https://github.com/rommapp/romm/pull/4869
- ci: cache mypy between runs and install only Playwright’s headless shell by @sdornan in https://github.com/rommapp/romm/pull/4873
- chore(trunk): upgrade plugins and linters, automate upgrades, scan all files weekly by @sdornan in https://github.com/rommapp/romm/pull/4870
- chore(trunk): Upgrade trunk by @github-actions[bot] in https://github.com/rommapp/romm/pull/4880
- ci: coverage upload, Docker PR check, API type drift check and other CI maintenance by @sdornan in https://github.com/rommapp/romm/pull/4875
- docs: fix the cloud environment setup script command in AGENTS.md by @sdornan in https://github.com/rommapp/romm/pull/4882
- chore(trunk): remove the no-emdash Trunk linter after a one-time sweep by @gantoine in https://github.com/rommapp/romm/pull/4884
- chore(storybook): Save State support by @andest01 in https://github.com/rommapp/romm/pull/4727
- perf: run the chroma-key placeholder check off the event loop by @sdornan in https://github.com/rommapp/romm/pull/4899
- ci: sign release images, gate on critical CVEs and scan for malware by @gantoine in https://github.com/rommapp/romm/pull/4890
- chore(deps): update dependency @types/node to v24 by @renovate[bot] in https://github.com/rommapp/romm/pull/4908
- chore(deps): update dependency @floating-ui/vue to v2 by @renovate[bot] in https://github.com/rommapp/romm/pull/4907
- chore(deps): update dependency pytest-cov to v7 by @renovate[bot] in https://github.com/rommapp/romm/pull/4911
- chore(deps): update dependency certifi to v2026 by @renovate[bot] in https://github.com/rommapp/romm/pull/4915
- test(tasks): cover scheduled cron description in TasksSection by @sdornan in https://github.com/rommapp/romm/pull/4918
- chore(deps): update dependency cronstrue to v3 by @renovate[bot] in https://github.com/rommapp/romm/pull/4917
- chore(deps): update dependency ipykernel to v7 by @renovate[bot] in https://github.com/rommapp/romm/pull/4919
- chore(deps): update dependency globals to v17 by @renovate[bot] in https://github.com/rommapp/romm/pull/4913
- chore(deps): update dependency streaming-form-data to v2 by @renovate[bot] in https://github.com/rommapp/romm/pull/4924
- chore(deps): update dependency vite-plugin-mkcert to v2 by @renovate[bot] in https://github.com/rommapp/romm/pull/4925
- chore(deps): update dependency md-editor-v3 to v7 by @renovate[bot] in https://github.com/rommapp/romm/pull/4920
- chore(deps): update dependency @vueuse/core to v15 by @renovate[bot] in https://github.com/rommapp/romm/pull/4909
- refactor(frontend): derive state that was stored and synced by hand by @sdornan in https://github.com/rommapp/romm/pull/4929
- chore(deps): update github actions by @renovate[bot] in https://github.com/rommapp/romm/pull/4910
- perf(scan): read only the selected ROMs’ gamelist entries by @sdornan in https://github.com/rommapp/romm/pull/4940
- perf(gamelist): validate media tag paths once per directory by @sdornan in https://github.com/rommapp/romm/pull/4943
- test(frontend): pin the timezone for pending-asset capture names by @sdornan in https://github.com/rommapp/romm/pull/4948
- chore(lint): remove the no-emdash-in-comment ESLint rule after a one-time sweep by @gantoine in https://github.com/rommapp/romm/pull/4887
- chore(deps): update dependency pinia to v4 by @renovate[bot] in https://github.com/rommapp/romm/pull/4921
- chore(deps): update md-editor-v3 to v7 and fix its breaking changes by @sdornan in https://github.com/rommapp/romm/pull/4922
- chore(deps): update dependency vuetify to v4 by @renovate[bot] in https://github.com/rommapp/romm/pull/4927
- refactor(db): drop stored columns that duplicate derivable data by @sdornan in https://github.com/rommapp/romm/pull/4930
- ci: grant issues: write to the build scan job by @sdornan in https://github.com/rommapp/romm/pull/4934
- chore(deps): bump markdown-it from 14.2.0 to 14.3.2 in /frontend by @dependabot[bot] in https://github.com/rommapp/romm/pull/4960
- chore(deps): bump urllib3 from 2.7.0 to 2.8.0 by @dependabot[bot] in https://github.com/rommapp/romm/pull/4964
- chore(deps): update dependency axios to v1.20.0 [security] by @renovate[bot] in https://github.com/rommapp/romm/pull/4966
- chore(deps-dev): bump brace-expansion from 2.1.4 to 2.1.7 in /frontend by @dependabot[bot] in https://github.com/rommapp/romm/pull/4961
- test(backend): build test rows through shared factories and enforce them with ruff by @sdornan in https://github.com/rommapp/romm/pull/4972
- chore(deps): update typescript to 6.0.3 and hold below 7 by @gantoine in https://github.com/rommapp/romm/pull/4963
- refactor: remove unused code and deduplicate copied helpers by @sdornan in https://github.com/rommapp/romm/pull/4975
- refactor(db): follow-ups to the derived-state fixes by @sdornan in https://github.com/rommapp/romm/pull/4969
- refactor(db): escape LIKE searches with SQLAlchemy’s autoescape by @sdornan in https://github.com/rommapp/romm/pull/4976
- docs: recommend pytest-xdist for local backend test runs by @sdornan in https://github.com/rommapp/romm/pull/4983
- perf(search): keep the FULLTEXT index on searches with short words or stopwords by @sdornan in https://github.com/rommapp/romm/pull/4970
- chore(deps): bump tornado from 6.5.8 to 6.5.9 by @dependabot[bot] in https://github.com/rommapp/romm/pull/4985
- chore(wcag): AA misc fixes by @andest01 in https://github.com/rommapp/romm/pull/4971
- perf: trim idle memory of the backend processes by @gantoine in https://github.com/rommapp/romm/pull/4981
- perf: keep task code out of the cron scheduler and idle RQ workers by @gantoine in https://github.com/rommapp/romm/pull/4982
- chore(deps): update frontend dependencies by @renovate[bot] in https://github.com/rommapp/romm/pull/4989
- chore(deps): update backend dependencies by @renovate[bot] in https://github.com/rommapp/romm/pull/4990
- chore(deps): upgrade SQLAlchemy to 2.1 by @sdornan in https://github.com/rommapp/romm/pull/5007
- refactor(db): replace deprecated noload() with raiseload() by @sdornan in https://github.com/rommapp/romm/pull/5009
- refactor(v2): share popover dismissal across RSelect, RComboboxField, RDateField and RMenu by @sdornan in https://github.com/rommapp/romm/pull/5010
- refactor(v2): route v2 server-backed search boxes through useDebouncedSearch by @sdornan in https://github.com/rommapp/romm/pull/5011
- refactor(v2): route remaining v2 clipboard writes through useClipboard by @sdornan in https://github.com/rommapp/romm/pull/5017
- test(backend): fix order-dependent test_saves / test_states db handler tests by @sdornan in https://github.com/rommapp/romm/pull/5018
- refactor(v2): track AppNav’s window scroll with useWindowScroll by @sdornan in https://github.com/rommapp/romm/pull/5025
- test(frontend): auto-unmount every test’s wrappers from the global setup by @sdornan in https://github.com/rommapp/romm/pull/5026
- test(frontend): give every test a fresh Pinia from the global setup by @sdornan in https://github.com/rommapp/romm/pull/5028
- test(frontend): share the vue-i18n test mock and let Vitest reset mocks, stubs and env by @sdornan in https://github.com/rommapp/romm/pull/5027
- refactor(v2): use VueUse for v2 debounces, intervals, the page title and avatar preview by @sdornan in https://github.com/rommapp/romm/pull/5035
- refactor(v2): use useTimeoutFn for v2’s one-shot timers and useEventListener for hotkeys by @sdornan in https://github.com/rommapp/romm/pull/5037
- refactor(v2): add useRouteQueryParam for URL-backed v2 view state by @sdornan in https://github.com/rommapp/romm/pull/5039
- refactor(v2): load self-fetching v2 components through useFetchState by @sdornan in https://github.com/rommapp/romm/pull/5038
- test(frontend): restore real timers globally and echo params from the shared i18n mock by @sdornan in https://github.com/rommapp/romm/pull/5043
- refactor(v2): use VueUse for v2’s rAF loops, listener pairs and grid observers by @sdornan in https://github.com/rommapp/romm/pull/5045
- chore(storybook): fix the RTooltip ParentAttach story so the tooltip renders by @sdornan in https://github.com/rommapp/romm/pull/5047
- refactor(v2): share v2’s visible-tab poll and move the last timers and RTooltip to VueUse by @sdornan in https://github.com/rommapp/romm/pull/5048
- refactor(v2): use VueUse for v2’s image previews, remaining observers, rAF loops and timers by @sdornan in https://github.com/rommapp/romm/pull/5052
- chore(frontend): tighten frontend typechecking by @sdornan in https://github.com/rommapp/romm/pull/5053
- chore(frontend): enable strictVModel and make RSelect, PlatformSelect and RTabNav generic by @sdornan in https://github.com/rommapp/romm/pull/5055
- chore(converto): bump rom-converto to v0.23.2 in both images by @DevYukine in https://github.com/rommapp/romm/pull/5041
- ci: cache frontend node_modules and skip npm ci on a lockfile match by @sdornan in https://github.com/rommapp/romm/pull/5044
- ci(e2e): copy frontend/assets into the preview build by @gantoine in https://github.com/rommapp/romm/pull/5056
- chore(frontend): split the frontend tsconfig into project references by @sdornan in https://github.com/rommapp/romm/pull/5057
- chore(frontend): prepare the gallery and grid-nav composables for noUncheckedIndexedAccess by @sdornan in https://github.com/rommapp/romm/pull/5058
- refactor(v2): render v2 QR codes with useQRCode from @vueuse/integrations by @sdornan in https://github.com/rommapp/romm/pull/5060
- chore(frontend): prepare the rest of the v2 app code for noUncheckedIndexedAccess by @sdornan in https://github.com/rommapp/romm/pull/5061
- chore(frontend): prepare the shared frontend code for noUncheckedIndexedAccess by @sdornan in https://github.com/rommapp/romm/pull/5062
- chore(frontend): enable noUncheckedIndexedAccess by @sdornan in https://github.com/rommapp/romm/pull/5063
- chore(frontend): prepare v2 and shared code for exactOptionalPropertyTypes by @sdornan in https://github.com/rommapp/romm/pull/5065
- chore(frontend): enable exactOptionalPropertyTypes by @sdornan in https://github.com/rommapp/romm/pull/5067
- perf(scan): skip the second write and reload for newly inserted roms by @LoneAngelFayt in https://github.com/rommapp/romm/pull/5070
- perf(roms): faster smart collection and filter value queries by @LoneAngelFayt in https://github.com/rommapp/romm/pull/5069
- refactor(backend): route ROM visibility through a single RomVisibilityFilter by @sdornan in https://github.com/rommapp/romm/pull/5064
- perf(config): read config.yml values with a plain dict walk by @LoneAngelFayt in https://github.com/rommapp/romm/pull/5071
- test(db): give worktree runs their own temporary test database by @gantoine in https://github.com/rommapp/romm/pull/5074
- perf(tests): faster database setup and cleanup in the backend suite by @LoneAngelFayt in https://github.com/rommapp/romm/pull/5073
- perf(frontend): smaller first load and faster vitest setup by @LoneAngelFayt in https://github.com/rommapp/romm/pull/5072
- refactor(v2): simplify keyboard/gamepad handling by @sdornan in https://github.com/rommapp/romm/pull/5084
- perf(frontend): load md-editor on demand instead of with the app by @LoneAngelFayt in https://github.com/rommapp/romm/pull/5080
- refactor(v2): share the scan form’s hash-matcher switches and scan-type select by @sdornan in https://github.com/rommapp/romm/pull/5099
- chore(deps): update frontend dependencies by @renovate[bot] in https://github.com/rommapp/romm/pull/5103
- chore(deps): update backend dependencies by @renovate[bot] in https://github.com/rommapp/romm/pull/5104
- chore(deps): update actions/upload-code-coverage action to v1.4.4 by @renovate[bot] in https://github.com/rommapp/romm/pull/5105
- chore(deps): upgrade apprise to 2.0 by @sdornan in https://github.com/rommapp/romm/pull/5102
- test(tasks): cover the WebP conversion task by @sdornan in https://github.com/rommapp/romm/pull/5112
- test(utils): read tags and covers from real audio files in each format by @sdornan in https://github.com/rommapp/romm/pull/5113
- test: platform fixture factories for the backend and frontend by @sdornan in https://github.com/rommapp/romm/pull/5119
- test: typed ROM fixtures, and no model casts in tests or stories by @sdornan in https://github.com/rommapp/romm/pull/5122
- refactor(metadata): share the indexed filename lookup across IGDB, Moby and SS by @sdornan in https://github.com/rommapp/romm/pull/5130
- refactor(metadata): share provider request retries and error mapping by @sdornan in https://github.com/rommapp/romm/pull/5132
- refactor(metadata): share heartbeats, filename id tags and the Moby rom builder by @sdornan in https://github.com/rommapp/romm/pull/5134
- chore(e2e): Update playwright with smaller focus by @andest01 in https://github.com/rommapp/romm/pull/4932
- chore(deps): update backend dependencies by @renovate[bot] in https://github.com/rommapp/romm/pull/5136
- chore(e2e): check every page for accessibility violations with axe by @andest01 in https://github.com/rommapp/romm/pull/4951
- chore(trunk): pin the CLI download to its sha256 by @gantoine in https://github.com/rommapp/romm/pull/5140
- chore(trunk): Upgrade trunk by @github-actions[bot] in https://github.com/rommapp/romm/pull/5137
- chore(trunk): fix the scheduled trunk check-all failures by @gantoine in https://github.com/rommapp/romm/pull/5141
- chore(deps): update dependency md-editor-v3 to v7.1.0 by @renovate[bot] in https://github.com/rommapp/romm/pull/5142
- chore(v2): let the theme toolbar drive every lib story by @sdornan in #5150
- refactor(v2): extract RSortHeader and make the index lists real tables by @sdornan in #5149
- chore(eslint): adopt @vue/eslint-config-typescript and lint with type information by @sdornan in #5148
- chore(dev): DEV_PROXY_TARGET to run the frontend against a remote RomM by @gantoine in https://github.com/rommapp/romm/pull/4763
- chore(storybook): add an input modality toolbar by @gantoine in https://github.com/rommapp/romm/pull/4791
- chore(locales): it_IT translation by @dms1e4 in https://github.com/rommapp/romm/pull/5006
New Contributors
- @fmustafayaman made their first contribution in https://github.com/rommapp/romm/pull/3931
- @yiannias made their first contribution in https://github.com/rommapp/romm/pull/4741
- @beemines made their first contribution in https://github.com/rommapp/romm/pull/4956
- @dms1e4 made their first contribution in https://github.com/rommapp/romm/pull/5006
- @ilyas-hallak made their first contribution in https://github.com/rommapp/romm/pull/5014
- @NotAFlightRisk made their first contribution in https://github.com/rommapp/romm/pull/5115
Full Changelog: https://github.com/rommapp/romm/compare/5.3.1…5.4.0-alpha.1