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>
Adds an RBox3D primitive that builds a rotatable, fake-3D game box from
three flat ScreenScraper scans (front, back, spine) using CSS 3D
transforms. Box proportions derive from the images themselves; it rotates
via pointer drag, arrow keys / gamepad D-pad, and the right analog stick,
drifts gently when idle, and honours prefers-reduced-motion.
The game detail hero (CoverColumn) upgrades to the spinning box when the
"3D box" boxart style is selected and the rom has the full set of faces,
falling back to the flat cover otherwise.
Backend: persist the box-2D-side (spine) scan locally, mirroring the
existing box-2D-back handling — new BOX2D_SIDE media type + box2d_side_path
on ss_metadata, opt-in via scan.media.
- RBox3D primitive + Storybook story (controls + keyboard-rotation play())
- useBoxFaces composable resolving the three faces + a `complete` gate
- box3d-alt i18n key across all locales
- backend BOX2D_SIDE persistence + tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019itLXRfJXGGbhPY3JyqnuN
Show a "where you left off" screenshot on the Home continue-playing rail and
the live-activity board, with a small cover-art thumbnail (PIP) in the corner
so the game stays identifiable. Both render at the image's natural aspect.
Backend:
- New shared util `continue_playing_screenshot(rom, latest_save)` resolving the
image in priority order: latest save's screenshot, then title screen, then
first gameplay screenshot (None → frontend falls back to cover art).
- `SimpleRomSchema.screenshot_path` populated only on the `last_played` query;
`get_latest_saves_for_roms` batch handler (+ tests).
- ActivityEntry / ActivityEntrySchema gain `screenshot_path`, computed from the
session player's latest save in both the socket and REST heartbeat paths.
Frontend:
- New shared `CoverArtPip.vue` (bottom-right 2D cover thumbnail), reused by
GameCard and ActivityCard.
- Home continue-playing rail uses `screenshot_path` + PIP, natural aspect (no
forced hero/style).
- Activity board: screenshot-forward cover + PIP, and a wrapping flex layout so
cards share a uniform height with natural-ratio widths (gallery-card
behavior).
- GameCover only keys the measured ratio by rom id for the rom's own cover, so
a `coverSrc` override (screenshot) never pollutes the gallery's ratio cache.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gate automatic account creation on OIDC login behind a new OIDC_ALLOW_REGISTRATION environment variable. Defaults to true, preserving the current auto-provisioning behavior; set it to false to run OIDC in an "existing users only" mode, where a login from an email without an existing RomM account is rejected with a 403 instead of silently creating one. Existing users are unaffected either way.
Adds the config constant, env.template and docs entries, and tests covering the enabled/disabled and existing-user paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The platform aspect_ratio setting is dropped from the UI and the API
(platform update body + response schema) — nothing consumed it for
rendering, and covers now size to their image's natural aspect.
- SettingsTab: remove the cover-style / aspect-ratio picker (and its
now-dead helpers, CSS, and unused imports); collapse to a single column.
- update_platform: drop the `aspect_ratio` body field; PlatformSchema no
longer returns it; utils/platforms stops seeding the default.
- Regenerate the affected frontend types (PlatformSchema, update body).
The DB column stays (out of the update/response scope; dropping it would
be a separate destructive migration) but is no longer read or written
through the API.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend:
- Resolve the acting user from the authenticated socket session on
connect instead of trusting the client-supplied user_id, so a client
can no longer spoof a "now playing" session for another user. Only
rom_id/device_id come from the payload.
- Emit activity:update/clear through the already-initialised socket
server instead of opening (and leaking) a fresh AsyncRedisManager per
REST heartbeat.
- Collapse get_all_active's per-key GET into a single MGET.
- Drop the pure pass-through _build_activity_entry helper.
Frontend:
- Remove all activity emits from the v1 EmulatorJS Player; the v2 shell
is the single driver of the activity lifecycle.
- Remove activity from the v1 UI entirely (Activity view, ActivityBtn,
ActivePlayers on game details, navigation, and the now-v2-only route).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Introduced new API endpoints for updating visibility of saves and states.
- Added `is_public` property to `SaveSchema` and `StateSchema`.
- Created new models for user saves and states with visibility attributes.
- Updated the `SaveDataTab` component to differentiate between "Mine" and "Community" sections.
- Implemented visibility toggle functionality for user saves and states.
- Enhanced localization files to include new strings for visibility actions.
- device/init returns a relative verification_path; the client joins it
with its own origin
- Render the v2 approval screen via the named v2 router outlet (was blank)
- DevicePair: RSpinner, keyboard-accessible scope chips, scrollable scopes;
DevicePairShell uses always-light overlay tokens (no hex)
Instead of always inferring "web" for browser-emitted activity events, look
the device up by device_id and use its client type, falling back to "web"
(the browser default) when no device record exists. Mirrors the REST
heartbeat endpoint, which derives device_type from device.client.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses review feedback: `db_name` is interpolated into CREATE DATABASE
statements via f-strings (identifiers can't be bind parameters), so validate
it up-front against a plain-identifier allowlist ([A-Za-z0-9_]+) and refuse
anything containing quoting/other characters, rather than rely on quoting.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses review feedback: granting ALL PRIVILEGES on *.* is overly broad,
especially against a shared DB instance. A database-level grant on the
`romm\_test%` wildcard pattern still lets the user CREATE the per-worker
databases (romm_test_gw0, ...) needed by pytest-xdist, while confining it to
that namespace — verified that out-of-namespace CREATE DATABASE is denied.
PostgreSQL needs no equivalent change: its CI service user (POSTGRES_USER)
is the container superuser.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add pytest-xdist and run the backend test suite across multiple workers
(`-n 4` in CI). Each worker gets its own database so the autouse
`clear_database` fixture can't wipe rows another worker is mid-test with:
- Rootdir `backend/conftest.py` sets a per-worker `DB_NAME`
(`romm_test_gw0`, ...) before any app module is imported, so each
worker's engine binds to its own database.
- `tests/conftest.py` creates the per-worker database on demand (mariadb/
mysql and postgresql paths) just before migrations run.
- The test user's grant is widened to `*.*` (setup.sql + CI) so it can
`CREATE DATABASE` for the workers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- main.py: await the cancelled log-forwarder task (suppressing
CancelledError) so its pubsub/lock cleanup finishes before shutdown.
- forwarder: only heartbeat the Redis lock while we still own it; if a
stall let another worker take it, relinquish forwarding to avoid
duplicate lines (the outer loop re-contends).
- endpoints/logs.py: derive MAX_LOG_LIMIT from LOG_BUFFER_SIZE so the
REST backfill never drifts from the producer's ring buffer.
- Logs.vue: append the download <a> to the DOM before click() (matches
the Patcher pattern) for cross-browser reliability.
- Add tests/endpoints/test_logs.py: non-admin 403, limit clamping to
[1, MAX_LOG_LIMIT], oldest-first ordering, and malformed-entry skip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The log-stream handler and forwarder deliberately swallow exceptions: the
handler is a best-effort mirror that must never raise into the app, and the
forwarder can't log its own failures without feeding back into the stream.
Annotate these with `# nosec` (with justification) so Trunk's bandit check
passes, keeping lines within black's width.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Introduced a new logs view for admin users, allowing real-time monitoring of backend logs.
- Implemented a log entry streaming mechanism using Socket.IO.
- Added filtering and searching capabilities for log entries.
- Created localized log messages in Spanish, French, Hungarian, Italian, Japanese, Korean, Polish, Portuguese, Romanian, Russian, Simplified Chinese, and Traditional Chinese.
- Updated router and sidebar components to include the new logs route.
- Enhanced user interface with tooltips and buttons for copying and downloading logs.
Drop the name_sort_key_custom flag/migration in favour of a flagless rule: a
key is "custom" when it no longer equals compute(name). Apply that consistently
across all three write paths so a manual sort key survives renames while a
derived key keeps following the name:
- @validates re-derives on name assignment only when the stored key still
matches the derived value; direct name_sort_key assignment stores a
normalized custom key (or reverts to derived when cleared). Handles both
kwarg orders at construction.
- update_rom mirrors the same check for the bulk update() path it bypasses.
- The edit endpoint only writes the key when the user actually changed the
field, delegating the untouched case to update_rom.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse the separate `sort_name` column into `name_sort_key`, which is now
the single user-settable sort field: always normalized and indexed for fast
ordering, derived from `name` by default, and overridable. A new
`name_sort_key_custom` boolean marks user/metadata overrides so they survive
renames and rescans.
- Drop the `roms.sort_name` column; repurpose migration 0085 to add
`name_sort_key_custom`.
- Derive the key via `@validates("name")` unless pinned custom; the edit
dialog, unmatch flow, and ES-DE gamelist <sortname> set custom keys.
- update_rom / scan_rom keep the columns in sync explicitly (bulk update and
construction bypass / reorder the validator).
- Frontend: edit field drives name_sort_key (empty when auto), api sends the
override only when custom, regenerated types updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The revision id '0084_add_roms_search_and_sort_indexes' (37 chars)
exceeded alembic_version.version_num VARCHAR(32), so every migration
run and test setup failed with 'Data too long for column version_num'
when stamping the revision. Rename it to '0084_roms_search_sort_indexes'
(29 chars) and update the 0085 down_revision link to match.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>