Commit Graph

4233 Commits

Author SHA1 Message Date
zurdi
5303130d15 Merge remote-tracking branch 'origin/master' into chore/permissions-system-rework 2026-06-26 15:35:41 +00:00
zurdi
4e686e1d74 fix(permissions): address PR review (Copilot + adversarial) findings
Resolves the CI blocker and a cluster of opt-out visibility "fail-open"
gaps surfaced in review of the granular permission system.

Security / correctness:
- admin oauth_scopes projection keeps canonical FULL_SCOPES order
  (order_scopes) instead of sorting alphabetically, fixing the red
  test_user.py::test_admin on MariaDB + Postgres.
- default-group hides no longer fail open: the resolver resolves the
  effective (own-or-default) group before the hidden-entity lookup.
- /roms/by-hash and /roms/by-metadata-provider now 404-mask hidden roms.
- USERS-entity grant no longer enables admin creation: add_user and
  invite-link require a real admin to mint admin accounts.

Visibility leaks closed on secondary read paths:
- feeds, sibling roms (list query + single-rom schemas), /stats counts
  and per-platform breakdowns, collection rom_ids/rom_count, search_rom.

Hardening / cleanups:
- firmware/platform PUT 404-mask hidden entities; group rename conflict
  returns 400 not 500; guard against removing the last default group;
  kiosk read-only enforced at the fine layer; add_hidden_entity rejects
  non-cascading entity types.

Frontend:
- permissionGroups.ensureLoaded coalesces concurrent callers on one
  in-flight request; permissions.setGrants resets isAdmin/hidden;
  CreateUserDialog no longer orphans a user when group assignment fails;
  HiddenGamesPicker search rows are native buttons (keyboard/gamepad);
  invite-role labels and group swatch aria-label use i18n; drop dead code
  (originalRole, unused permissionsApi export).

AI assistance: changes authored with Claude Code (Claude Opus), driven by
the Copilot review and a multi-agent adversarial review, then verified
(backend pytest, frontend typecheck/vitest, i18n parity, trunk).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 12:37:58 +00: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
zurdi
bf801cb314 feat(permissions): narrow user role to admin/user and adapt v1
Collapse the Role enum from viewer/editor/admin to two kinds (admin/user);
non-admin access now comes entirely from permission groups + overrides.

Backend:
- Role -> StrEnum {USER, ADMIN}, VARCHAR-backed (native_enum=False); Role.coerce
  maps legacy/unknown strings (incl. in-flight invites) to USER.
- Resolver: group-less users fall back to the default group (dropped the
  role-based legacy fallback); kiosk caps all non-admins to read-only.
- OIDC editor/viewer claims both map to USER (env var names unchanged).
- Migration 0092 converts the native enum to VARCHAR and normalizes
  ADMIN->admin, VIEWER/EDITOR->user.
- Updated endpoints, conftest fixtures, role/oidc/parity/db-handler tests, tools.

Frontend:
- v2 + v1 user management now use admin/user (dropdowns, defaults, getRoleIcon).
- Regenerated types (Role = 'user' | 'admin'); added role-user i18n to all locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 22:42:51 +00:00
zurdi
de3f35df6e feat: added foundational work for the new permissions system 2026-06-25 21:49:00 +00: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
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
8d75474890 Merge remote-tracking branch 'origin/master' into chore/permissions-system-rework 2026-06-25 13:44:38 +00: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
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
zurdi
722f3008b6 feat: rework permissions system to use backend hydration and introduce new permission models 2026-06-25 09:30:14 +00:00
y1lm0z
6c79336c53 Turkish language support 2026-06-25 03:19:01 +03: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
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
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
Georges-Antoine Assi
ececc2ec91 Port v2 patcher to server-side per-ROM flow
Mirror the v1 change that moved patching from a browser web worker to the
server-side /roms/{id}/patch endpoint operating on files already in the
library.

- Rewrite v2 Patcher view to read base/patch files from currentRom and
  POST to the patch endpoint, dropping the worker, rom-patcher imports,
  window globals, and dropzones.
- Add canPatch + patch() to useGameActions and surface a Patcher entry in
  the per-ROM more-menu (gated on nested files).
- Add a Patch button to the GameDetails FilesTab header.
- Remove the now-broken standalone Patcher links (route is per-ROM) from
  UserMenu and SettingsSidebar; refresh stale nav doc comments.
- Add patcher.missing-from-fs i18n key.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:16:50 -04:00
Georges-Antoine Assi
52163633e5 Merge branch 'master' into claude/server-side-rom-patching-EaQck 2026-06-23 07:01:07 -04:00
Georges-Antoine Assi
111fc969be run fmt 2026-06-22 19:40:56 -04:00
Claude
d7c5ab7e06 feat(v2): RBox3D faces right, self-spins, and coasts on flick
- Default orientation now faces right (initialYaw flipped).
- Idle auto-spin actually resumes: the time-based check moved into the RAF
  loop, since a computed reading performance.now() cached and never restarted
  after the first interaction. Quiet window is 2s.
- Flick momentum without a JS decay loop: the release velocity is handed to
  the box as one extra rotation and a CSS ease-out curve coasts it to a stop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019itLXRfJXGGbhPY3JyqnuN
2026-06-22 23:23:37 +00:00
Claude
1b02bf6a3e fix(v2): RBox3D adopts box-2D dimensions even when cached
Read the front (box-2D) and spine image natural ratios on mount, not only on
the load event — a cached cover decodes before the listener binds, which left
the box stuck at the default ratio instead of the real box-2D proportions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019itLXRfJXGGbhPY3JyqnuN
2026-06-22 22:17:22 +00:00
Claude
ab2b61783b fix(v2): RBox3D rotates on drag instead of selecting faces
Add user-select: none to the box and pointer-events: none to the faces so a
drag always lands on the root (which owns the rotation listeners) and never
starts a text / image selection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019itLXRfJXGGbhPY3JyqnuN
2026-06-22 22:12:48 +00:00
Georges-Antoine Assi
d38be187f3 Merge branch 'master' into claude/youthful-wozniak-ocjhc0 2026-06-22 18:04:37 -04:00
Georges-Antoine Assi
c4bfbac872 Merge branch 'master' into v2-ui-tweaks-gg 2026-06-22 17:45:59 -04:00
Claude
77980a79ec feat(v2): interactive 3D box art on game detail hero
Adds an RBox3D primitive that builds a rotatable, fake-3D game box from
three flat ScreenScraper scans (front, back, spine) using CSS 3D
transforms. Box proportions derive from the images themselves; it rotates
via pointer drag, arrow keys / gamepad D-pad, and the right analog stick,
drifts gently when idle, and honours prefers-reduced-motion.

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:40:56 -04:00
Georges-Antoine Assi
e471efb987 feat(v2): morph gallery cover into the player on Play
Clicking Play at the center of a gallery cover now runs the shared-element
view transition into the /ejs (EmulatorJS) or /ruffle hero, matching the
card → details morph.

- useGameActions gains an optional `coverEl` resolver; `play()` wraps the
  navigation in `morphTransition` (cover → player hero, same `rom-cover-<id>`
  tag the player paints statically) and awaits the push so the snapshot is
  taken after the player renders. GameCard supplies its GameCover box.
- The player heroes only seeded `rom` from `currentRom` (set via GameDetails),
  so a direct gallery→play left `rom` null and the `v-if`-gated hero never
  rendered — nothing to morph into. Seed a lightweight `heroSeed` SimpleRom
  from the gallery store (new `galleryRoms.getRomById`) so the cover paints
  its morph tag immediately; `rom` fills in on mount. Play is disabled until
  the full payload loads.
- Enable hover-motion on both player heroes so the cover spin / hover video
  work there too.
- Arcade systems (arcade / neogeoaes / neogeomvs) skip the cartridge slot-in
  animation (new `isArcadeSystem`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 12:46:31 -04:00
Georges-Antoine Assi
4a49a762a8 fix(v2): port cover animations to v1's compose-by-property model
Rework useCoverAnimation to mirror v1's useGameAnimation, which composes
two motions on different CSS properties so they don't fight: the spin
owns `transform: rotate` (set per-frame) while the disc/cartridge slot-in
owns `margin-top` (eased in CSS with an overshoot). A single transform for
both couldn't ease the slide independently of the per-frame spin.

- CD launch spins at max while sliding the cover down into the drive;
  cartridge launch seats fully into its bay (1/3 height).
- Drop the cartridge hover animation entirely — matches v1, which only
  spins discs and plays the hover video on hover.
- Enable hover-motion on the EmulatorJS and Ruffle player heroes so the
  hover spin / video work there too.
- Fix a `.ame-cover__img` typo (missing "g") that was silently breaking
  the cover image base styles, and ease `margin` in CSS on `.game-cover__img`
  so the slide composes with the bloom reveal instead of overriding it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 12:19:03 -04:00
zurdi
e232e9e00e test(v2): align cover-ratio debounce test with 350ms re-pack delay
The re-pack debounce in useGalleryCoverRatios was raised from 150ms to
350ms (c5d844e1) to avoid a re-pack storm during fast scroll, but the
test still advanced fake timers by 150ms and expected the debounced
ratioVersion bump to have fired — so it asserted 1 and got 0. Advance
by the new 350ms delay at all three sites. Pre-existing failure on
master, unrelated to this branch's changes; bundled here to unblock CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 16:09:13 +00:00
zurdi
8c20fd6547 feat(logging): enhance logging module with dynamic module name resolution and add module filtering options 2026-06-22 15:51:22 +00:00
Georges-Antoine Assi
7b8b35fbc1 fix miximage view preview 2026-06-22 10:06:03 -04:00
Georges-Antoine Assi
e10a715cf3 improve hotpath 2026-06-22 08:07:24 -04:00
Georges-Antoine Assi
76417cb4b1 Merge branch 'master' into v2-ui-tweaks-gg 2026-06-22 07:49:39 -04:00
Georges-Antoine Assi
367626c21e some UI tweaks 2026-06-22 07:48:52 -04:00
zurdi
c5d844e118 feat(v2): improve gallery responsiveness and optimize game actions handling 2026-06-22 11:36:39 +00:00
zurdi
8a4d286cfa feat(activity): add total sessions label to activity localization files
- Added "total-sessions" key to various language JSON files for activity localization.
- Updated ActivityCard component to reflect changes in the activity view.
- Enhanced Activity view to display a live session counter with a tooltip.
- Introduced virtual scroll debugging to monitor performance in the gallery.
- Adjusted layout styles for better responsiveness and visual consistency across components.
2026-06-22 09:23:39 +00:00
Georges-Antoine Assi
e7d383b285 style(v2): right-align list-view cover in its column
Cover hugs the title side of its fixed-width column (and the skeleton
matches), so the empty space sits on the left and the cover reads as
attached to its title.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:43:59 -04:00
Georges-Antoine Assi
41ec6f4de6 fix(v2): give list-view cover its own column so titles align
With natural-aspect covers, the list-row cover lived inside the title cell
and its variable width pushed each row's title/meta to a different x. Add a
dedicated fixed-width (64px) cover column to the shared list grid template —
big enough for any portrait→square game cover — so the title column starts
at the same x on every row. Header and skeleton pick it up via the shared
column config; the cover renders left-aligned at its natural aspect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:33:09 -04:00
Georges-Antoine Assi
44016129b5 feat(v2): list-view covers respect natural aspect; scale placeholder title
- Gallery list view: drop the `fixed` opt-out on its GameCard so the
  row thumbnail renders at the cover's natural aspect (fixed height,
  natural width) like everywhere else. Removes the now-unused `fixed`
  prop / class / CSS branch from GameCard.
- CoverPlaceholder: the title now scales with the cover box (container
  query units, clamp(8px, 8cqmin, 18px), em padding) instead of a fixed
  12px — readable on a tiny list thumb, proportionate on the detail hero.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:09:30 -04:00
Georges-Antoine Assi
cdb1f17902 feat(v2): add section headings to HLTB strip and screenshots in overview
The overview's screenshots and How-Long-To-Beat blocks rendered with no
label, unlike the related-games sections. Wrap both in the same labelled
section pattern (renamed `overview-tab__related-*` → `__section-*` since it
now covers non-related blocks too). The HLTB heading is gated on a
`hasHltb` computed so it doesn't show when the strip is empty.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 20:11:33 -04:00
Georges-Antoine Assi
14a7b40475 details cleanup 2026-06-21 20:10:07 -04:00
Georges-Antoine Assi
72fb96ec4c fix(v2): consistent gap between cover and fields in the edit dialog
The edit dialog's hero used a fixed 240px cover column with the cover
centered, so a natural-width cover left variable leftover space — making
the gap to the fields vary by cover shape. Size the column to the cover
(`auto`) so the gap is exactly the grid gap for any cover, and give it a
touch more room (24px).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:57:00 -04:00
Georges-Antoine Assi
c8362ed22e feat(v2): use natural cover aspect ratio across remaining surfaces
Extend natural-aspect cover rendering beyond the gallery to the rest of
the app (continue-playing stays a fixed 16:9 hero, by request).

- GameCard size tiers now render fixed-height / natural-width like the
  default card (size just picks the height); covers EditRomDialog (lg)
  and ManageCollectionsDialog (xs) for free. Adds a `fixed` opt-out for
  dense aligned tables.
- GameListRow (compact list view) opts into `fixed` so its cover column
  stays uniform for row alignment.
- Compact fixed-column thumbnails (ScanPlatformRow, DeleteRomDialog,
  RefreshMetadataDialog, ActivityCard) stop cropping (object-fit
  cover → contain): the whole cover shows at its true aspect while the
  slot stays uniform.

Deliberately unchanged: collection mosaics (composite collage art),
save/state screenshots (16:9, not covers), and the provider cover-picker
comparison grids (uniform tiles aid side-by-side selection).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:46:01 -04:00
Georges-Antoine Assi
74ef594609 feat(v2): render the Random Pick cover at its native aspect ratio
Swap the hand-rolled RImg + useCoverArt cover resolution in
RandomPickWidget for the shared GameCover, which measures the image's
natural ratio. The thumbnail now renders at a fixed 70px height with
natural width (no crop), matching the gallery, and the widget's cover
plumbing (coverSrc / coverContain / placeholder fallback) collapses into
GameCover.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:24:05 -04:00
Georges-Antoine Assi
662dc89a24 Merge branch 'master' into aspect-ratio-no-no 2026-06-21 19:20:09 -04:00