Bulk downloads only ever name a couple dozen ROMs, so the id list fits
in the URL and `GET /roms/download?rom_ids=` stays as it was. Removes
`POST /roms/download/selection`, the Redis-backed selection handler,
the `selection` query param, and the download-failure snackbar.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
An aborted claim deleted the blank it created even after adoption had
stored the container card on it, while the adoption record stayed. The
next claim then skipped the prompt, created a new blank and hydrated
it, wiping the container's original card. The new catch-all in
claim_session made any late failure hit this, not only a failed
hydrate or launch. discard_blank_card now keeps a card that holds a
version, so the next claim mounts it.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
- Validate each exclude list where it is read, so a new path cannot skip
validation and the rule has one home.
- Move useEscapable to composables/ and use it in RMenu and
MatchRomBodyGrid, which hand-rolled the same stack wiring.
- Drop the Escape branches in RSelect and RDateField that the escape
stack's capture listener now preempts.
- Debounce the gallery search with lodash like the rest of v2.
- Drop a cached-client assertion that held by construction.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
- Time stream play sessions only for joiners; the backend records the
claim holder's session on release, so timing it here counted it twice.
- Reject non-string exclude-list entries (YAML reads 001 as an int).
- Clear the shared dialog close mock between combobox tests.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* Check the active sort in the gallery sort menu
The grid toolbar's sort menu (and its phone-width mirror) marked the
active axis by colour alone. It now also shows a leading check, the same
way the version switcher and sibling menus mark the current item.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015iCApu4gYrGBWDX8JD9JH6
* Share the sort menu's selection mark and test the phone menu
Both sort menus now take their active variant and check from one
selectionMark helper, and the toolbar test covers the phone-width menu
as well as the inline one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015iCApu4gYrGBWDX8JD9JH6
* Reset the toolbar test's breakpoint in teardown and drop a restating comment
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015iCApu4gYrGBWDX8JD9JH6
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
0.30 moved to json-schema-ref-parser 14, whose safe URL resolver
refuses localhost, so `npm run generate` failed in the api-types check.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Vite 8.0.16 pins rolldown 1.0.3, so the rolldown 1.2.11 override broke
the build. Vite 8.3.1 needs rolldown ~1.2.9 and clears min-release-age.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
report_task_failure can run in the worker parent when a work horse is
killed, so it now enqueues notify_task_failure on the default queue
instead of loading the notification stack there.
enqueue_task defaults to the queue its spec names. The streaming worker
runs as PreloadingWorker, which imports the enabled scheduled tasks of
its queues, so the reaper's per-minute horse inherits its stack. Spec
tests now read config in a fresh interpreter instead of comparing a
value with itself.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Loads every model where the engine is built rather than per job, so only
processes that touch the ORM pay for it. Resolves task paths with RQ's
import_attribute, references the scan failure callback by path, hoists
the recommendations builder's registry import now that the registry
loads no task modules, and gates push-pull on its spec.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Loads every model before a job resolves its task, so the mappers
configure in a work horse that imported a single task module. Moves the
httpx2 import into RemoteFilePullTask.run, builds the push-pull job meta
from its spec, gates the scheduled scan on its spec, and widens the
loaded-modules test to cover task modules and httpx2.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A text field claimed every arrow, and a synthetic key never moves the caret,
so a D-pad that landed in one was stuck there. useGamepad now flags the arrows
it dispatches, and for those useSpatialNav steps the caret itself, moving
focus on up and down and once the caret reaches the text's edge. Keyboard
arrows, number fields, and open popups keep their arrows.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
ConfirmDialog focused Cancel on the next tick, but RDialog's own initial focus
ran after it and landed on the header's close button. Mark Cancel autofocus,
which RDialog already prefers, so a stray A or Enter cancels as intended.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Addresses Greptile's review on #4971: rounding reported 100 at 99.6%.
Flooring keeps the value a whole number.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
onPlay started the play session only if the state was already 'playing'
when it returned, but a claim stays 'loading' until the launch-ready socket
push, which usually lands later. Start the session when the game starts
running and flush it when it stops.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XZW9ChvmiTKXYYWY6JvTKU
The search box was only written by typing, and the refetch only ran from
the debounce, so a search term changed by navigation (the platform view
reuses the shell across /platform/:id) left a stale query in the box.
Move the debounced search into useDebouncedSearch, which mirrors the store
term into the box, and refetch from the shell's filter watch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XZW9ChvmiTKXYYWY6JvTKU
RSelect, RComboboxField and RDateField never registered on the escape
stack, whose window capture listener runs first. Escape (or pad B) with
one of them open inside a dialog closed the dialog and left the popover
open; outside an overlay, pad B navigated back. Add useEscapable and
register each popover while its panel is shown.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XZW9ChvmiTKXYYWY6JvTKU
claim_session released the container only at the hand-placed abort_claim
calls before each planned refusal. Any other error after the claim was won
(a DB or Redis error, say) left the container reserved for every player
until the claim went stale, and could orphan the blank memory card.
Release the claim, and the blank card it created, on any such error.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XZW9ChvmiTKXYYWY6JvTKU
The cached client kept its own copy of the GetRomsParams to query mapping,
which had drifted: it dropped publishers, developers, physical,
has_soundtrack, the HLTB length bounds, released_days and the with_* flags.
Export buildGetRomsQuery from the API client and use it in both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XZW9ChvmiTKXYYWY6JvTKU
The exclude lists were spread into a set before validation ran, so the
isinstance checks could never fail: 'exclude.platforms: ps2' excluded the
folders 'p', 's' and '2', and a number crashed startup with a TypeError.
Validate the raw YAML values instead, and read only list values.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XZW9ChvmiTKXYYWY6JvTKU
The unmatch branch of update_rom nulled 15 ids but not gamelist_id, while
clearing gamelist_metadata, so an unmatched ROM that came from a
gamelist.xml still counted as identified. Build the reset (and the audit
record of what was unmatched) from METADATA_SOURCE_COLUMNS plus sgdb_id.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XZW9ChvmiTKXYYWY6JvTKU
The not-identified check in scan_rom listed 13 provider ids by hand and
missed tgdb_id and libretro_id, so a ROM matched only by Libretro (or by a
TGDB id from Hasheous) took the early return: it skipped the SteamGridDB
lookup and missing_from_fs reset, while Rom.is_identified counted it as
matched. Derive the check from METADATA_SOURCE_COLUMNS instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XZW9ChvmiTKXYYWY6JvTKU
Home and End go to the ends of the row, and with Ctrl to the first and
last item of the grid. A row of one cell (list view) has nowhere to go
inside it, so there Home and End move through the grid too. PageUp and
PageDown move about one viewport of rows, keeping the column. The
gallery and Missing games scroll the virtualised list to the edge row
and wait for it to load before focusing it.
The list row's click, Space and context-menu handlers move onto its
name link, which already takes every click on the row, so the row
element carries no handlers of its own.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Pico8, Ruffle, JsDos and EmulatorJS each set and cleared the flag by hand next
to gameRunning, with their own unmount resets. Each now mirrors gameRunning
through usePlayingWhile, which clears the flag on dispose. JsDos hands input
back at unmount rather than in teardown; every exit from a running JsDos
replaces the document, so the difference is not observable.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Spatial nav used to bail whenever an overlay was open, and no dialog moves
focus on arrows, so a pad could only reach a dialog's first control:
ConfirmDialog's Confirm was unreachable app-wide. It now moves focus among the
controls of the topmost overlay's panel, including a dialog over a running
game, which replaces Stream's own exit-dialog arrow handler.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>