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>
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>
Add live tracking of which users are actively playing which games, covering
both browser-based EmulatorJS sessions and external devices via a heartbeat
endpoint. Surfaces activity on a dedicated /activity page and as an indicator
on individual game detail pages.
Backend
- New handler/activity_handler.py stores ephemeral "now playing" state in
Redis with a 90s TTL keyed by (user_id, device_id), plus a per-ROM reverse
index for fast lookup.
- New endpoints/activity.py exposes GET /api/activity, GET /api/activity/rom/{id},
and POST /api/activity/heartbeat (for external devices).
- New endpoints/sockets/activity.py handles browser activity:start /
activity:heartbeat / activity:stop events, broadcasts activity:update /
activity:clear to all connected clients, and cleans up on socket disconnect.
Frontend
- Pinia activity store syncs via Socket.IO and provides per-ROM getters.
- ActivePlayers component on GameDetails shows who is currently playing a
title, with avatar tooltips.
- New Activity view lists every live session with game cover, user, platform,
and elapsed time.
- ActivityBtn in MainAppBar with a live-count badge routes to the page.
- EmulatorJS Player emits start/heartbeat/stop events and tears down the
heartbeat interval on exit.
No DB migration required; all state is Redis-resident.
https://claude.ai/code/session_01WzWu5XEEYcAc3EJcfteiFd