Surface never-scanned platform folders in the scan picker#3847
Merged
Conversation
A platform folder created on disk (optionally bound in Library Management) has no database row until its first scan imports a ROM, so it was invisible to the scan platform picker and a first scan could not be triggered for it. The picker also filtered to rom_count > 0, hiding even zero-ROM database platforms (e.g. those created via romm-cli). Backend: - Add GET /platforms/filesystem returning folders on disk without a database row yet (locally-resolved metadata, id -1, rom_count 0). - scan_platforms / the scan socket now accept platform_fs_slugs so a folder can be scanned by slug; database platforms stay accepted by id. Frontend (v2): - Fetch filesystem folders, key the scan picker by fs_slug, include zero-ROM database platforms, and tag never-scanned folders. Fixes #3832 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Greptile SummaryThis PR makes never-scanned platform folders available in the v2 scan picker. The main changes are:
Confidence Score: 4/5Explicit invalid selections can start a full scan, and completed scans leave stale duplicate picker entries.
backend/endpoints/sockets/scan.py; frontend/src/stores/platforms.ts Important Files Changed
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
backend/endpoints/sockets/scan.py:760
**Rejected Selection Scans Everything**
When a client submits only stale, renamed, or invalid slugs, the preceding filter leaves `selected_slugs` empty and this fallback starts a scan of every filesystem platform. A targeted request must use the scan-all fallback only when the client supplied no platform selection.
```suggestion
has_selection = bool(platform_ids or platform_fs_slugs)
platform_list = sorted(selected_slugs if has_selection else fs_platforms)
```
### Issue 2 of 2
frontend/src/stores/platforms.ts:38-41
**Scanned Folders Stay Unscanned**
After a first scan creates a database row, the scan lifecycle refreshes `allPlatforms` but leaves the old entry in `filesystemPlatforms`. This getter then shows the same `fs_slug` twice, including a stale `id: -1` row still marked as unscanned; deduplicate by `fs_slug` while preferring the database entry.
```suggestion
scannablePlatforms: ({ allPlatforms: all, filesystemPlatforms: fs }) =>
uniqBy([...all, ...fs], "fs_slug").sort((a, b) =>
a.display_name.localeCompare(b.display_name),
),
```
Reviews (1): Last reviewed commit: "Surface never-scanned platform folders i..." | 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
Explain the changes or enhancements you are proposing with this pull request.
Fixes #3832.
A platform folder created on disk (and optionally bound in Library Management) has no database row until its first scan imports a ROM, so it was invisible to the Scan platform picker and a first scan could not be triggered for it. The picker also filtered to
rom_count > 0, hiding even zero-ROM database platforms (e.g. ones created viaromm-cli). The backend scanner already works in folder-slug space, so the gap was purely that a never-scanned folder could not be seen or targeted.Backend
utils/platforms.py: extracted the shared "build aPlatformSchemafrom local metadata" logic into_build_unmatched_platform(), and addedget_filesystem_platforms()returning folders on disk without a database row yet (slug resolved viaPLATFORMS_BINDING/PLATFORMS_VERSIONS,id: -1,rom_count: 0, no network calls).endpoints/platform.py: newGET /platforms/filesystem(scopePLATFORMS_READ), declared before/{id}.endpoints/sockets/scan.py:scan_platforms()and thescansocket handler acceptplatform_fs_slugs. Selected database platforms (by id or slug) are always honored; a bare slug is accepted when it maps to a folder on disk; an empty selection still scans everything.Frontend (v2, the default UI)
services/api/platform.ts+stores/platforms.ts:getFilesystemPlatforms(), afilesystemPlatformsstate slice,fetchFilesystemPlatforms(), and ascannablePlatformsgetter (all database platforms including zero-ROM plus unscanned folders).v2/components/shared/PlatformSelect.vue: supportsitem-key="fs_slug"and an opt-inmark-unscanned+unscanned-labelmarker forid < 0folders.v2/views/Scan.vue: the picker is keyed byfs_slug, fetches filesystem folders on mount, tags never-scanned folders, and emitsplatform_fs_slugs.scan.folder-not-scannedkey, translated across all 18 locales.v1 (
src/views/Scan.vue) is frozen and left unchanged; it keeps working against the backwards-compatible backend (it still sends platform ids).AI assistance disclosure
This PR was written with AI assistance (Claude Code, Opus 4.8). The implementation, tests, and translations were AI-generated and reviewed by the author.
Checklist
Please check all that apply.
Screenshots (if applicable)
N/A — verified via typecheck, backend + frontend test suites, i18n parity/sort checks, and Trunk. A full in-browser pass (both themes / input modalities) was not run and is recommended before merge.
🤖 Generated with Claude Code