fix(home): stop scanning the library filesystem on every home page load - #4069
Open
Spinnich wants to merge 1 commit into
Open
fix(home): stop scanning the library filesystem on every home page load#4069Spinnich wants to merge 1 commit into
Spinnich wants to merge 1 commit into
Conversation
The v2 home page requested GET /api/setup/library on every visit, which walks every platform directory under the library path to count files. That hint only exists for brand-new instances, but it fired for everyone: 17s per home page load on an 83k-game library, 38s on slower storage, and the result was thrown away because the library wasn't empty. The frontend watcher was wired to `isEmpty` with `immediate: true`, so it ran during setup, before any of the page's data requests had started. All the `fetching*` flags were still false and the stores were still empty, so the library looked empty and the walk kicked off. Frontend: onMounted now awaits the initial fetches and flips an `initialLoadDone` flag. The watcher and the empty-state `v-if` both gate on `initialLoadDone && isEmpty`, so the request and the render can no longer disagree about whether the library is empty. Backend: get_setup_library_info() returns early with an empty `existing_platforms` once the database holds ROMs, since the on-disk hint is meaningless then. Gated on ROMs alone, not platforms: platform rows with zero ROMs are exactly the case the hint exists for, so those still walk. Fixes rommapp#4063 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Greptile SummaryThe PR prevents the v2 home page from requesting an expensive filesystem snapshot until its initial data requests have settled and the library is confirmed empty.
Confidence Score: 5/5The PR appears safe to merge with no concrete blocking or independently actionable non-blocking issues identified. The frontend now waits for its launched initial requests before evaluating the empty state, while the backend preserves the setup response contract and avoids enumeration only after a valid ROM existence probe succeeds. Important Files Changed
Reviews (1): Last reviewed commit: "fix(home): stop scanning the library fil..." | Re-trigger Greptile |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #4063
The v2 home page called
GET /api/setup/libraryon every page load, even on afully populated library. That endpoint walks the ROM library on disk (
iterdir()per platform directory) to build a first-run "here's what we see on disk" hint,
and the home page threw the result away because the library isn't empty.
Measured cost of the wasted walk:
the OS page cache doesn't rescue it)
Root cause.
loadLibraryInfo()was driven bywatch(isEmpty, ..., { immediate: true }).An immediate watcher runs during
setup(), which is strictly earlier thanonMounted, so at that moment no data request has even started: every storefetching*flag is stillfalseand every store array is still empty.isEmptytherefore read
truefor any library, and the walk fired unconditionally.Fixed on both sides of the stack, since either one alone leaves a sharp edge:
onMountednow collects its conditional fetches and awaitsPromise.allSettledbefore flipping a newinitialLoadDoneflag. Thewatcher and the empty-state
v-ifboth gate onshowEmptyState = initialLoadDone && isEmpty, so the filesystem hint isrequested only once the library is confirmed empty.
allSettledratherthan
allso a rejected fetch still releases the gate./setup/libraryreturns early withexisting_platforms: []when the database already holds at least one ROM, so any other caller (or an
older frontend) can't trigger the walk either.
Note on scope of the user-visible symptom. This is a wasted-request fix, not
a visible-flash fix. I verified with a
requestAnimationFrameframe sampler thatthe empty-library hero rendered in 0 frames on a populated library both before
and after the change:
onMountedsets the fetching flags before the first paint,so only the setup-time watcher was ever wrong. Please don't expect a visual
difference on a populated library; the difference is one fewer multi-second
request per page load.
Design note for reviewers. The backend bail-out is gated on ROMs only,
deliberately not on "platforms and ROMs". A ROM row implies a platform row via
FK, and a database with platform rows but zero ROMs is precisely the case the
filesystem hint exists to serve, so that case must still walk. Both behaviours
are pinned by tests.
Files modified
frontend/src/v2/views/Home.vueonMountedawaitsPromise.allSettledof the initial fetches, then setsinitialLoadDone. NewshowEmptyStatecomputed gates both the watcher and the empty-statev-if.immediate: trueremoved from the watcher.frontend/src/v2/views/Home.test.tsbackend/endpoints/heartbeat.pyget_setup_library_inforeturns early with an emptyexisting_platformswhendb_rom_handler.has_any_rom(). Placed after the existing scope check, and still returnsdetected_structure+supported_platforms.backend/handler/database/roms_handler.pyhas_any_rom()existence probe (SELECT id LIMIT 1, notCOUNT(*)).backend/tests/endpoints/test_heartbeat.pyfs_platform_handler.get_platformsis never called), and the walk still happens for platform rows with no ROMs.Testing notes
uv run pytest→ 2704 passed, 2 skipped.npm run test→ 615 passed across 48 files.vue-tsc --noEmitclean.trunk fmt && trunk checkclean./api/setup/libraryrequests (pre-fix: 1, firing inthe same millisecond as the other initial requests).
detected" / "16 games detected" chips still rendering as before.
rather than adding one (
rows:5 → rows:4 → hero @377msbecamerows:5 → hero @271ms).response_modeland the responsekeys are unchanged, so no
npm run generateis needed.Reviewer attention
that the issue text did not specify. If you'd rather it also considered
platform rows, that flips the behaviour of
test_get_setup_library_info_walks_when_platforms_have_no_roms.a second pair of eyes:
SHOW_SETUP_WIZARDis only true when there are zeroadmin users, at which point there are no ROMs, so the bail-out cannot trigger
mid-wizard. The early return also preserves
detected_structureand all 460supported_platformsentries the wizard reads.Promise.allSettledsemantics: a failed initial fetch still flipsinitialLoadDone, which is intentional. A user whose requests all failed willsee the empty state and one filesystem-hint request, which is the same thing
they'd have seen before.
Checklist
AI assistance disclosure
Per
CONTRIBUTING.md: this change was written with AI assistance (Claude Code).The AI wrote the tests and implementation, ran the test suites, and performed the
browser verification described above. I reviewed the diff, and the measurements in
the linked issue are my own from my instances.