All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog.
- Sources management — unified screen to add, configure, disable, and remove RomM/SMB/FTP/Web sources with controller-grade focus handling and per-system mapping
- RomM 4.8 token pairing — Client API Token authentication via QR code or manual entry, with live server probe and connection validation
- Per-card source dots — colored dot indicators on game cards showing which provider(s) serve each game; stacked dots when available from 2+ sources
- Manual source creation — type picker UI for adding SMB, FTP, or Web directory sources outside of RomM
- Per-system mapping editor — assign which systems a manual source provides, with visual mapping count
- Re-pair action — refresh expired or borrowed RomM tokens directly from the Sources screen
- Onboarding chooser — single-question welcome flow ("How do you store your ROMs?") replacing the old multi-step wizard, with path-specific setup for local, network, and RomM users
- ROM folder picker — select your ROM base folder during onboarding; local filesystem scan runs on every chooser path
- Merge-only mode — failover removed entirely; merge is now the sole multi-source strategy (was unused dead code, merge handles all cases better)
- Default merge enabled — new and legacy configs default to
mergeMode: truewithout user intervention - Settings restructured — Sources surfaced as the primary entry point; "Edit Consoles" renamed to "Edit Systems" (read-only, links to Sources for changes)
- Unified RA setup — RetroAchievements configuration consolidated into a single lightweight screen shared between onboarding and settings
- Transparent config migration — v2 configs automatically upgrade to v3 format on first launch with no user action required; dual-write keeps old code paths functional
- RomM Server tile — replaced by the more capable Sources management screen
- Scan Library tile — use "Sync All" from the quick menu instead
- Failover mode — merge handles all multi-source scenarios; failover was never exposed in UI
- "Skip for now" onboarding option — led to an empty home screen with no way forward
- Sync timestamps now persist correctly from
syncAllandsyncSystem(were silently lost) - Auto-merge providers when a system has 2+ contributing sources
- Installed games remain visible after disabling their source
- Legacy providers tagged as managed so disable/remove actually cleans them up
- Alternative sources persisted in database so multi-source games survive sync cycles
- RA screen focus clipping (inner borderRadius now smaller than outer)
- RomM platform filter (#11) —
platform_idsquery parameter was serialized in a way the RomM API ignored, causing every system to receive the entire library mixed across platforms. Combined with the larger RetroAchievements metadata payloads in recent RomM versions, this also caused sync to stall at 0/N for users with bigger libraries. Filter now uses repeated query params (platform_ids=1&platform_ids=2) so the server actually applies it. - Sync receive timeout — bumped from 30s to 90s. A single 500-ROM page with embedded RA achievements and screenshots can be several MB, especially with high server latency.
- Local fallback on remote failure — when a remote source (RomM/SMB/FTP/Web) fails for a system, the sync now falls back to a local-only filesystem scan so users don't lose visibility on locally-present ROMs. Applied to all four sync paths (full sync, smart sync, library scan, single-system sync).
- Persistent error pill — when a sync fails, the status pill in the top-left now turns red, pulses gently, and stays visible until the next sync starts (was amber and auto-dismissed after 6s — easy to miss).
- Detail screen focus visibility — focus indicators now use white borders and glow instead of accent-color-only styling, ensuring clear visibility regardless of system theme color
- Correct focus tracking — sub-widgets (primary button, icon buttons, screenshots, version cards) only show focus highlight when their section is actually focused; eliminates phantom focus on multiple elements simultaneously
- Action buttons anchored — Download/Delete and Favorite/Share/Shelf buttons are now anchored to the bottom of the left column in landscape, consistent across all detail views
- Variant picker redesign — compact tag pills with region flags, marquee-scrolling filenames for long ROM names, wider overlay (55% screen width), D-pad left/right blocked to prevent focus leak
- Other Versions section — now shows only RomM/IGDB siblings instead of duplicating local variants already visible in the variant picker
- Universal 4:3 and 16:9 layout — replaced hardcoded pixel constraints with screen-relative
clamp()values across 9 overlay/dialog widgets to prevent overflow on narrow screens and wasted space on wide ones - MarqueeText widget — new shared ticker-scroll widget for long text in version cards and sibling entries; scrolls when focused, static ellipsis otherwise
- Variant picker focus restore — returning from the download queue while the variant picker is open now correctly restores D-pad focus to the picker
- Icon button resize on focus — removed text labels from Favorite/Share/Shelf buttons that caused multi-line wrapping on 4:3 screens
- LanguageBadges overflow — removed language flags from version cards that caused RenderFlex overflow in tight layouts
- Extracted
MarqueeTexttolib/widgets/marquee_text.dartfor reuse across variant picker and sibling section isSectionFocusedparameter threaded throughActionButtonsRow,ScreenshotsCarousel,OtherVersionsSection- Detail screen
_buildPrimaryActionSectionand_buildIconButtonsSectionnow receiveisFocusedfrom parent
- Smart sync with cooldown — app launch now uses
syncSmart()which skips systems whose last sync is within the configured cooldown window, drastically reducing redundant network traffic on frequent launches - Per-system auto-sync toggle — each console can individually opt out of automatic sync via a new "Auto-sync on app launch" toggle in console configuration; disabled systems only sync manually from the quick menu
- Sync cooldown setting — new Settings entry (Always / 15 min / 30 min / 1 hour / 2 hours / 6 hours) controls the minimum interval between automatic re-syncs per system (default: 1 hour)
- Per-system sync from quick menu — Start menu on the home screen now shows "Sync [System Name]" with a human-readable "Synced X ago" subtitle for the currently selected console; cancels any running auto-sync, syncs the single system, then resumes remaining stale systems
- Sync All from quick menu — dedicated "Sync All" entry replaces the old "Retry Sync" option; forces a full sync of every configured system regardless of cooldown
- Shelf removal from game detail — when a game is already in all shelves, the "Add to Shelf" action flips to "Remove from Shelf" with a picker showing containing shelves; filter-rule-matched games use exclude instead of remove
- Quick menu subtitles —
QuickMenuItemgains an optionalsubtitlefield rendered below the label in a smaller font - ROM file sharing — Share button on game detail now shares the actual ROM file via system share sheet (requires game to be installed); re-enters immersive mode after share sheet closes
- Post-settings sync — returning from Settings now only force-syncs newly added consoles instead of clearing all freshness and re-syncing everything
- Shelf picker dialog — accepts a custom
titleparameter (used for "REMOVE FROM SHELF" vs "ADD TO SHELF") - Per-system last sync persistence —
StorageServicetracks last sync time per system ID via SharedPreferences, surviving app restarts - Sync completion awaiting —
LibrarySyncService.waitForCompletion()returns a Future that resolves when the current sync finishes, enabling clean cancel-then-act flows - Game detail initial focus — Download/Delete button is now focused by default when entering the detail screen
- Per-system sync list in Settings — the per-console sync items with game counts have been removed from the System tab in favor of the quicker quick-menu-based sync flow
- "Retry Sync" quick menu entry — replaced by the more flexible "Sync [System]" and "Sync All" options
SystemConfig.autoSyncfield (defaulttrue) with JSON serialization andcopyWithsupportConsoleSetupState.autoSyncin onboarding state, wired throughOnboardingController.setAutoSync()SyncCooldownNotifier/syncCooldownProviderin app_providers.dart with cycle-through UIStorageService.getLastSyncTime()/setLastSyncTime()for per-system ISO 8601 persistenceLibrarySyncService.syncSmart()with cooldown, forceSystemIds, and autoSync filteringLibrarySyncService._syncCompleterforwaitForCompletion()across syncAll, syncSmart, syncSystem_resumeAutoSyncAfterManualflag in HomeView for cancel→manual→resume flow- 143 new test lines in library_sync_service_test.dart and settings_widgets_test.dart (1666 total)
- Blurred background bleed —
ImageFilteredblur on the game detail cover background painted beyond widget bounds; wrapped inClipRectto prevent the blurred image from bleeding through at the bottom edge
- Per-system sync — sync individual consoles from Settings instead of all-at-once (thanks @yangeric, #7)
- ROM count per system — Settings now shows game count badges for each configured console (#7)
- Configurable sync timeout — choose between 1, 2, 5, or 10 minutes for slow connections (Synology NAS etc.) (#7)
- Game counts provider —
gameCountsPerSystemProviderfor stable post-sync ROM counts (#6)
- Cover search progress > 100% — consecutive "Search Game Covers" runs no longer accumulate counters; generation-based cancellation ensures clean state (thanks @yangeric, #8)
- DB cascade deletes — deleting games now also removes orphaned
game_metadataandra_matchesrows - Background refresh orphan safety —
saveGamesorphan deletion is now explicit (deleteOrphansparameter) to prevent incomplete fetches from wiping cached games
LibrarySyncService.syncSystem()for single-system syncsyncTimeoutProviderwith cycle-through UI in SettingsCoverPreloadService._generationcounter for stale-worker protection- 78 new tests (1615 total) covering cover reset, DB cascade, sync timeout, per-system sync
- ZIP install status inconsistency — game detail screen now correctly shows "Installed" for archive files (.zip, .rar) that exist in the ROM folder, matching the game list badge (thanks @yangeric, #5)
- ZIP deletion — deleting a game that was kept as a .zip archive now works correctly instead of silently failing
- autoExtract setting ignored — the per-system auto-extract toggle is now respected; when off, downloaded .zip files are moved as-is to the ROM folder instead of being unconditionally extracted
- RomM pagination timeout — large libraries (7k+ ROMs per platform) no longer time out; per-page error handling returns partial results on failure instead of nothing (thanks @yangeric, #4)
- Provider timeout separation — RomM gets a dedicated 10-minute timeout for paginated fetches; FTP/SMB/Web keep a tighter 60-second safety net
- Flaky test —
clearFilters resets allno longer fails intermittently under full test suite load
- 3DS extension support — added .cci, .cxi, and .app ROM formats (thanks @yangeric, #1)
- RomM Switch downloads — games served as ZIP archives by RomM are now correctly detected and extracted via Content-Disposition header parsing (thanks @gulasch, #2)
- Stale game cache after deletion — deleted games now immediately disappear from the game collection instead of lingering until the next background refresh
- Home carousel performance — AnimatedBuilder now wraps individual items instead of the entire PageView, reducing unnecessary rebuilds
- Game list performance — favorites provider uses selective watch to avoid rebuilding when unrelated favorites change
- Visible systems query — single batch
systemsWithCache()query replaces N individualhasCache()calls - Sync error reporting — failed sources are now tracked per-system with specific error messages ("2 sources unavailable" instead of generic "Offline — cached data")
- Library screen —
setEqualsguard prevents unnecessary rebuilds when installed files haven't changed - Thumbnail migration — batch size increased from 3 to 15 (ThumbnailService has its own capacity guard)
- Memory —
gameMetadataProvidernow uses.autoDisposeto free metadata when detail screen is closed
DatabaseService.deleteGame()for targeted cache entry removalDatabaseService.systemsWithCache()for batch system presence checksLibrarySyncState.failedSystemsreplaces singleerrorstring with per-system failure map- 170+ new database upsert tests (cover preservation, orphan cleanup, cross-system isolation, large batches)
- Updated onboarding and sync tests for remoteSetup step and failedSystems format
- Console store-style game detail — redesigned detail screen with structured layout, section headers, and download area inspired by digital storefronts
- IGDB metadata — RomM games now display genre, developer, release year, game modes, and summary via a glassmorphic "About This Game" card (fetched from RomM's IGDB data)
- Description overlay — full game summary accessible from the quick menu when metadata is available
- Variant picker overlay — pressing A on multi-version games opens a dedicated picker with all variants, install status, and per-variant download/delete actions
- Game metadata database — new
game_metadatatable (DB v8) stores IGDB metadata separately from game entries, surviving library re-syncs
- Quick menu consolidation — Tags, Description, Filename toggle, and Achievements are now accessible from the quick menu instead of dedicated button shortcuts
- Detail screen layout — portrait mode uses scrollable layout with adaptive cover aspect ratio; landscape uses two-column layout with expandable info card
- Download action button — redesigned as a standalone widget with distinct states (download, delete, installed, adding, unavailable) and variant count badge
GameMetadataInfomodel withhasContent,genreList,averageRatinghelpersgameMetadataProvider(FutureProvider.family) for async metadata loadingRommRomextended to parsesummary,genres,companies,first_release_date,game_modes,average_ratingRommProvider.fetchGames()saves metadata as fire-and-forget side effect- 194 new API service tests covering metadata parsing edge cases
- RetroAchievements integration — connect your RA account to track achievements, verify ROMs via hash matching, and view per-game progress directly in R-Shop
- Achievements screen — dedicated viewer with earned/locked badges, points, progress bar, mastery status, and full D-pad navigation
- RA onboarding step — optional setup during first-run wizard with connection test and skip option
- RA config screen — manage credentials (encrypted via SecureStorage) from Settings with connection testing
- Post-download hash verification — downloaded ROMs are automatically hashed and matched against the RA database in the background
- RA sync service — 3-phase background sync (catalog fetch → name matching → hash verification) with 24h freshness cache and cancellation support
- RA badges on game cards — achievement count and match type (gold for name match, green for hash verified) displayed on every card; mastery outline when fully completed
- Add-to-queue toast — animated bottom-right notification when a game is added to the download queue
- Hide empty consoles — new toggle in Settings → Preferences to hide systems with no games from the home screen
- Sync badge — now shows dual pills for library sync (cyan) and RA sync (gold) with independent progress tracking
- Game detail screen — RA info section below metadata showing match status, progress bar, and "View Achievements" button; quick menu gains "Achievements" option
- Download overlay — visual refinements and better state display
- SystemModel — 15+ systems now carry RA console IDs (NES, SNES, N64, GB, GBC, GBA, Mega Drive, SMS, Game Gear, 32X, Atari 2600/7800, Lynx, NDS)
- Library screen — deduplication of installed entries when same game exists in multiple formats
- Cover preload service — improved reliability and error handling
- Database schema v7 — new tables:
ra_games(catalog cache),ra_hashes(hash index),ra_matches(match results) - Hash computation for 10+ systems: simple MD5, NES (iNES header strip), SNES (copier header), NDS (multi-section), Lynx, Atari 7800
RaNameMatcherwith 4-tier fallback: exact → contains → No-Intro filename → fuzzy (Levenshtein)- New providers:
raGameProgressProvider,raRefreshSignalProvider,raMatchResultProvider,raSyncServiceProvider - 1,209 tests (up from 1,069) — new suites: RA hash service, RA models, RA name matcher, expanded database and onboarding tests
- Native SMB — replaced smb_connect library with Kotlin MethodChannel service (
SmbService.kt), enabling folder downloads, progress reporting, and reliable timeout handling on Android - Folder downloads — games stored as multi-file directories (bin/cue, m3u) can now be downloaded as complete folders via SMB and FTP
- Gamepad button icons — SVG icon set (Xbox, PlayStation, Nintendo Switch) for context-aware controller hints
- RomM config screen — full server management (add/edit/remove) with connection test, directly accessible from settings
- Network constants — centralized timeout values (
NetworkTimeouts) shared across all providers - File utilities — crash-safe atomic file move (
moveFile) with staging and cleanup
- Onboarding rework — redesigned setup wizard with streamlined console configuration, local folder detection, and RomM integration
- FTP provider — host validation (hostname, IPv4, IPv6), injection protection, configurable timeouts
- Web provider — security hardened directory parsing (path traversal, control chars, oversized hrefs filtered)
- Download service — folder-aware downloads for SMB and FTP protocols with per-file progress
- Friendly errors — expanded user-facing error mapping for network, auth, and provider failures
- Console HUD / Quick Menu / Control Button — simplified rendering with gamepad icon integration
- 1,069 tests (up from 970) — new suites: SMB provider (14), FTP provider (8), Web provider (12), FocusSyncManager (32), OverlayPriorityManager (14), file_utils (5), friendly_error expansions
smb_connectdependency removed (replaced by native Kotlin implementation)NativeSmbServiceDart wrapper forcom.retro.rshop/smbMethodChannelNativeSmbDownloadHandle/NativeSmbFolderDownloadHandledownload handle types
- Stable release — R-Shop exits beta
- SVG platform icons — all 29 system icons migrated from PNG to crisp SVG format
- Android package restructure — migrated from
com.example.r_shoptocom.retro.rshop - Network security config — dedicated XML configuration for local network protocols
- Test coverage — 950+ tests covering controllers, services, models, and utilities
- Code quality — zero TODO/FIXME markers, zero silent catches, all error paths logged
- Dependency hygiene — all dependencies pinned to exact versions
- New test suites: GameListController (43 tests), GameMergeHelper (12 tests), ImageHelper (19 tests)
- 8 additional test files covering app config, audio manager, config parser, onboarding, providers, and cover preload
- Custom Shelves — create personal game collections with manual curation, filter rules (by system, region, language), or hybrid mode; supports reordering, renaming, and per-shelf sort modes
- Device Info Service — adaptive memory tiering (low/standard/high RAM) that auto-tunes image cache sizes, grid cache extents, and cover preload pools for low-end handhelds
- Shelf Picker Dialog — quick-add games to shelves from library and game detail screens
- System Selector Overlay — filter library view by system with visual system badges
- Settings screen refactored — split into Preferences, System, and About tabs with extracted
DeviceInfoCardwidget (1048→604 lines) - Download overlay refactored — extracted 7 widgets to
lib/widgets/download/(DownloadItemCard, CoverThumbnail, PulsingDot, LowSpaceWarning, StatusLabel, DownloadProgressBar, DownloadActionButton) (1477→793 lines) - Shelf edit screen refactored — extracted GameListOverlay, TextInputDialog to shared library widgets (1108→631 lines)
- RomM onboarding refactored — extracted RommConnectView, RommSelectView, RommFolderView, RommActionButton (1405→122 lines); state classes moved to
onboarding_state.dart(1713→1362 lines) - Library screen refactored — extracted ReorderableCardWrapper, LibraryEntry to dedicated widgets (1490→1386 lines)
- Dependency pinning — all 16 remaining caret-range dependencies pinned to exact resolved versions for reproducible builds
- New models:
CustomShelf,ShelfFilterRulewith JSON serialization - New providers:
CustomShelvesNotifier/customShelvesProviderfor shelf CRUD DeviceInfoServicewithMemoryTierclassification- ~20 new test files covering download queue manager, unified game service, library sync, thumbnail service, custom shelves, database service, config storage, image cache, storage service, and widget tests
- Crash log service — local ring-buffer log file (~500KB) captures all uncaught errors with timestamps and stack traces; persists across sessions in app cache
- Export Error Log — new Settings entry (under System) shares the crash log via the system share sheet for easy bug reporting; only visible when log contains data
- Disk space pre-check — downloads are rejected with a clear error when device storage drops below 1 GB
- HTTP download depth guard — the
_downloadHttpresume-restart path now enforces a single-retry limit, preventing infinite recursion if the server keeps returning mismatched content lengths - FocusSyncManager index safety —
ensureFocusNodes()clamps_selectedIndexafter pruning disposed nodes;validateState()clamps_targetColumnwhen column count changes — prevents focus jumping to invalid positions during rapid grid resizing - Zone alignment —
WidgetsFlutterBinding.ensureInitialized()andrunApp()now execute inside the samerunZonedGuardedzone, eliminating the "Zone mismatch" warning on startup - Overlay priority release — all scope classes (
OverlayFocusScope,DialogFocusScope,SearchFocusScope,ExitConfirmationOverlay) deferrelease()viaFuture()indispose(), fixing the Riverpod "cannot modify provider during widget tree build" crash - Detail screen layout — system name badge wrapped in
Flexiblewith ellipsis overflow, fixingRenderFlexoverflow on narrow screens with long system names (e.g. "PlayStation 2") - ConfigModeScreen dispose — audio manager cached in
initStateto avoidref.read()after widget is disposed - Download queue lookup —
getDownloadById()uses a simple loop instead offirstWhere+ try/catch, eliminating noisy "Bad state: No element" log spam during queue restore
- Overlay priority crash — releasing overlay tokens during widget unmount no longer throws Riverpod state modification errors (was causing "At least listener of the StateNotifier threw an exception" crashes)
- FocusSyncManager focus loss — selected index could point to a disposed
FocusNodeafter the item count decreased, causing silent focus failures
- New
CrashLogServicesingleton (lib/services/crash_log_service.dart) withlog(),logError(),getLogFile(),clearLog(),getLogContent() crashLogServiceProviderinapp_providers.dart, overridden inmain.dart- Global error handlers (
FlutterError.onError,PlatformDispatcher.onError,runZonedGuarded) now write to crash log in addition todebugPrint - Removed spammy
debugPrintinFocusSyncManager._enforceFocus()for deferred focus (fired on every scroll)
- Thumbnail pipeline — persistent isolate-based thumbnail generator (400px JPEG) with background migration on startup and proactive cover preloading during library scans
- ROM status providers — real-time ROM installation tracking via filesystem watchers and download-completion listeners, replacing manual polling
- Installed files provider — central isolate-scanned index of all installed ROM files across every system
- Cover preload — new Settings entry to batch-generate thumbnails for all games
- About section — app version, GitHub/Issues links, and Easter Egg tagline in Settings
- Zip extraction limit — increased from 2 GB to 8 GB
- Smart cover loading — thumbnail-first display with magic-byte validation, JPEG re-encoding for corrupt cache entries, and scroll-suppressed loading to reduce jank during fast scrolling
- Controller button styling — pill-shaped shoulder/trigger buttons, per-layout face button colors (Xbox green/red/blue/yellow, PlayStation palette), and shape painters for Nintendo +/− buttons
- Quick menu hints — face button hints now show layout-correct color palettes
- Game card performance — replaced
AnimatedScale/AnimatedContainerwith staticTransform.scale/Containerfor smoother grid scrolling;SelectionAwareItemusesValueNotifierto rebuild only affected cards on selection change - Search overlay — extracted
SearchableScreenMixin(shared by GameListScreen and LibraryScreen) and movedSearchOverlaywidget fromfeatures/game_list/widgets/towidgets/for cross-screen reuse - FocusSyncManager moved from
features/game_list/logic/tocore/input/for use by Library and Scan screens - Library screen — now uses
SearchableScreenMixin,SelectionAwareItem,FocusSyncManager, and scroll suppression for consistent behavior with GameListScreen - Image cache rate limiter — cancellable pending requests, increased concurrent fetch limit (50), and host-level rate-limit detection
- Database schema v4 — adds
thumb_hashandhas_thumbnailcolumns; thumbnail flags preserved across game list refreshes OverlayGuardedAction— generic reusable guarded action replaces per-screen private action classes
- Zip bomb protection — extracted archive size capped at 2 GB; extraction aborts with clear error if limit exceeded
- Web provider path traversal — directory listing parser rejects absolute URLs and
../href values - Overlay priority teardown —
OverlayFocusScope,DialogFocusScope, andSearchFocusScopeuseaddPostFrameCallbackwith try/catch instead of rawFuture(), preventing "disposed notifier" crashes on fast screen transitions - Android backup disabled —
android:allowBackup="false"prevents unintended data restore from breaking app state - Grid navigation guard —
_GridNavigateActionnow checksoverlayPriorityProviderinisEnabled, preventing D-pad navigation while an overlay is open - Focus restoration —
mainFocusRequestProvidernow set centrally inConsoleScreenMixin.initState
- New dependencies:
image: ^4.3.0,crypto: ^3.0.6 GameItem.hasThumbnailfield added;copyWithextended accordinglyadjustColumnCount()helper extracted toConsoleScreenMixin- Deleted 4 obsolete files:
animated_background.dart,radial_glow.dart,folder_analysis_view.dart,search_overlay.dart(game_list copy)
- Scan Library screen — Settings entry opens animated console grid with per-system scan progress, game count badges, and completion summary
- Smart onboarding auto-detection — detects existing ROM folders at common paths (
/storage/emulated/0/ROMs,/Roms,/roms) with scan, create, pick, or skip options - Cache-first game list loading — lists load instantly from SQLite cache, then silently refresh from remote providers; UI only updates if the list actually changed (filename-diffed)
- Offline indicator — amber "Offline — cached data" toast on failed sync; sync badge shows failure state
- Provider reordering — D-pad or arrow buttons to change provider priority in console configuration panel
- Test & Save — single button tests provider connection and auto-saves on success (replaces separate Save button)
- User Guide (
docs/USER_GUIDE.md) — comprehensive guide covering all features, controls, supported systems, and troubleshooting
- ROM format coverage expanded across 10+ systems — GameCube (ISO/GCM/CISO), Wii (WBFS/WIA/CISO), PS2 (CSO), PS3 (PKG), PSP (PBP), Mega Drive (BIN/SMD), Dreamcast (CDI/GDI), Saturn (ISO), Arcade (7z), N64 (V64), SNES (SMC)
- Isolate-based local scanning — filesystem scanning offloaded to a Dart isolate via
compute()for smoother UI - Library sync freshness — 5-minute cache prevents redundant re-syncs;
clearFreshness()forces refresh after config changes - Sync covers local-only systems —
syncAll()now includes systems without remote providers - Batched install-status checks — processes in parallel batches of 20
- Filter passthrough — games with no region/language metadata now pass through filters instead of being excluded
- Console grid badges — local-only systems show blue folder badge instead of green provider checkmark
- Post-settings re-sync — config reloaded and freshness cleared after returning from settings
- Library installed detection — correctly matches extracted ROM files (e.g.,
Game.zip→Game.iso) - Library-based search — Y button on home navigates to Library with search open, replacing standalone global search overlay
- GameDetail variant index clamped to valid range (prevents crash on variant list changes)
- Search overlay focus handling improved
GlobalSearchOverlayremoved (675 lines) — replaced by Library withopenSearch: trueRepoManagerandRomHeaderParserremovedarchivedependency removed frompubspec.yamlGameMergeHelperextracted for dedup logic (remote-vs-local merge, archive expansion, multi-file detection)SystemModelgainsarchiveExtensions,allRomExtensions,allGameExtensions, andisGameFile()- Unused providers cleaned up from
app_providers,config_providers,download_providers LibrarySyncServiceextended withdiscoverAll(),isFresh(),hadFailuresstate
- Library screen — unified cross-system game browser with All/Installed/Favorites tabs, search, sort modes (A-Z / by system), and adjustable grid zoom (LB/RB)
- Background library sync — automatic provider sync on launch with live progress badge on home screen
- Quick Menu (Start/+ button) — contextual overlay with shortcuts to Search, Settings, Zoom, and Downloads
- Home grid layout — toggle between carousel and grid view on the home screen; grid columns adjustable via LB/RB
- ROM header parser — extracts internal game titles from GB, GBC, GBA, NDS, and SNES ROM headers (raw + ZIP)
- Favorite toggle (Select/- button) — quick-favorite from game detail screen
- Tab switching (LB/RB) — navigate filter tabs in Library and Filter overlay
- Local-only filter — new toggle in filter overlay to show only locally installed ROMs
- BaseGameCard replaces old game card — unified design with system badge, installed indicator, favorite heart, variant count, and provider label
- Version card simplified — significant refactor removing redundant layout logic
- ConsoleHud refactored — cleaner slot rendering, consistent spacing, proper embedded vs positioned modes
- Filter overlay — improved layout with local-only toggle and multi-tier filtering
- Global search — results now show provider label and region flags consistently
- Controller layout preference persisted across sessions
- Home layout preference (carousel/grid) persisted across sessions
- Download overlay HUD position — button legend was stuck top-left instead of bottom-right (AnimatedOpacity wrapping Positioned broke Stack layout)
- Quick Menu downloads option now shows whenever queue has any items (including completed/failed), not just active+queued
- Favorite names migration cleans up legacy IDs on app start
- Duplicate library entries for multi-file ROMs — remote archive merge now adds extracted folder names to dedup set (bin/cue games no longer appear twice)
- ROMs in subdirectories not detected —
scanLocalGames,exists, anddeletenow check subdirectories for all ROM extensions, not just multi-file formats
- New
QuickMenuOverlaywidget with overlay priority and controller-aware shortcut hints AdjustColumnsIntentconsolidates zoom controls across screensToggleOverlayActionusesonTogglecallback instead of publishing a state requestSyncBadgewidget for real-time sync progress displayLibrarySyncServiceasStateNotifier<LibrarySyncState>homeLayoutProvider,homeGridColumnsProvider,controllerLayoutProviderin app_providers
Warning
Migration Notice: Upgrading from versions <= 0.9.3 to 0.9.4 or newer requires a fresh installation due to significant backend database and config architecture changes. Legacy configurations will not transfer over cleanly.
- Global search activated (Home screen, Y button) — cross-system search with region flags and tag badges
- Local-only mode — consoles without a provider show locally scanned ROM files with a banner hint
- FTP download progress — real-time per-chunk progress reporting instead of staying at 0%
- Download inactivity watchdog — 60-second stall detection with clear error message
- Gamepad key fix — intercepts mismatched logical keys on key-up/repeat for certain gamepad drivers (AYN Thor etc.)
- Provider type badge on version cards (RomM, SMB, FTP, WEB)
- Download overlay sectioned list (Downloading / Queued / Complete headers) with ID-stable focus and auto-scroll
- Download overlay auto-close when queue empties
- SMB domain auth field
ProviderConfig.validate()andshortLabelhelpersshowConsoleNotification()— themed floating SnackBar used app-widegetUserFriendlyError()— maps raw exceptions to readable messages
- RomM Config screen reworked — focus-aware glow borders, D-pad navigable fields, connection test as toast, per-console sync status with bulk "Update stale" action
- Settings screen — RomM Server item re-enabled, concurrent downloads widget with chevrons, Reset App moved to HUD X-button
- Home view empty-state shows HUD bar with Settings/Exit actions; loading state is a black screen (no flash)
- Game detail loading shows game name + system-colored spinner
- Game list header shows target folder path and local-only banner
- Game grid context-sensitive empty states (search miss, filter miss, local-only empty, connection error)
- Platform icons compressed (~70% smaller file sizes)
- All action shortcuts use
includeRepeats: false(no more repeated fires on button hold; D-pad retains repeats) OverlayFocusScope/DialogFocusScope—_hasClaimedflag prevents double-release of overlay priority- RomM cover fallback chain (CDN → small → large → first screenshot)
UnifiedGameServiceprovider calls wrapped in 30s timeoutRommApiService/WebProvider— connect 15s / receive 30s timeouts- README updated with supported systems table and Building from Source section
- Zip Slip vulnerability — Android ZIP extraction validates canonical path before writing
- Path traversal —
_safePathusesp.basename()in bothDownloadServiceandRomManager - Atomic config write —
ConfigStorageService.saveConfigwrites to.tmpthen renames - DB init race condition —
DatabaseServiceuses singlestatic Future<Database>?guard - AudioManager BGM reinit loop —
_hasAttemptedReinitflag prevents recursive retry - Retry timer leak —
DownloadQueueManagertracksTimerinstances, cancels on dispose - FailedUrlsCache unbounded growth — replaced
Set<String>withMap<String, DateTime>+ 5min TTL - Delete dialog defaults to CANCEL (selection index 1, not 0)
- FTP download cancel now disconnects the FTP session
totalBytesclamping — treats<= 0as unknown so progress bar works correctly- Global search
providerConfigpropagation — results open with correct provider RepoManagerDio connection leak —dio.close()infinallyblockGameDetailController/GameListController— disposed-guard prevents post-disposenotifyListeners()- Global search Escape/B from text field moves focus to results instead of closing
- Search overlay left/right arrows no longer leak to grid while editing
DownloadItemfully immutable (all fieldsfinal), serializessystemId+providerConfigDatabaseServiceschema v3 (addsprovider_configcolumn with migration)gamepad_key_fix.dartinstalled at app startup viamain()- Download overlay refactored into
_buildSectionedList/_buildCard/_buildSectionHeaderhelpers
- RomM onboarding wizard — guided first-run setup with connection test, platform auto-discovery, and folder assignment
- Game list filter overlay (X button) — filter ROMs by region and language with per-console persistence
- Global search overlay infrastructure (cross-system search, wired but trigger disabled until v0.9.4)
- Android foreground service — downloads continue in background with a persistent notification
- Download queue persistence — queued and errored downloads survive app restarts
- Automatic download retry with exponential backoff (3 attempts, 5s/15s/45s)
- Configurable concurrent download limit (1–3) in Settings
- RomM config screen for editing server URL, API key, and credentials
- Local folder scanner and fuzzy system-ID matcher for onboarding folder assignment
- RomM ROM list fetching is now paginated (pages of 500, no more timeouts on large libraries)
- RomM platform API accepts both
itemsandresultsresponse keys (multi-version support) - Download overlay action button is context-aware (Cancel / Retry / Clear)
- Filter and search are mutually exclusive in the game list
- Onboarding console setup no longer requires a provider (target folder only is valid)
- RomM ROM fetch used a malformed
platform_idsquery parameter - Download overlay showed "Retry" for completed items
- Game grid passed unfiltered variant lists when filters were active
- Home view carousel index jumped after system list refresh
- Added
flutter_foreground_taskdependency - Android manifest: foreground service, notification, and battery optimization permissions
FilterState/ActiveFiltersmodel extracted;RommSetupStateadded to onboarding controllerDownloadQueueManagernow takesStorageService;GameDetailControllertakesDownloadQueueManagerlocal_folder_matcher_test.dartunit tests
- Installed indicator LED strip on game cards for downloaded ROMs
- Shared
ConsoleSetupHudwidget for onboarding and config-mode screens ConsoleHudslot-based API (a,b,x,y,start,select,dpad) replacing raw button lists- Auto-scroll on focus for
ConsoleFocusableviaScrollable.ensureVisible
- Onboarding rework — streamlined flow with guard checks for provider test/save actions
- Input system: global Actions now use
isEnabled()overlay checks instead of inline guard clauses NavigateActioncooldown (100ms) prevents rapid-fire navigation on DPAD holdOverlayFocusScopeclaims priority and requests focus in a single operation- Focus state restoration uses
getFocusState()public API instead of directStateNotifier.stateaccess ConfigModeScreensimplified, shares HUD logic with onboarding
- DPAD hold producing duplicate navigation by removing
tick()feedback fromConsoleFocusable ConsoleFocusable.didUpdateWidgetcorrectly handles focus node swapsExitConfirmationOverlayoverlay priority lifecycle (set ininitState, reset indispose)
- Removed
GameSourceService(replaced by unified provider system) SystemModelrestructured for multi-source provider configsFocusScopeObserverandOverlayScopecleanup
- Multi-source provider system — each console can use Web, SMB, FTP, or RomM sources
- RomM server integration with automatic platform matching via IGDB
- JSON-based configuration system with import/export support
- Config editor in settings to add/remove/edit consoles after onboarding
- Unified Game Service with merge and failover strategies across multiple sources
- Interactive onboarding wizard with per-console setup
- Mouse/touch support for all focusable widgets (click to focus and activate)
- Volume sliders now respond to drag and tap input
- Home screen only shows configured consoles
- Input debouncing for all HUD buttons and action classes (prevents double-tap actions and duplicate sounds)
- Renamed
romPathtotargetFolderto resolve path confusion in the download system - Download queue backward-compatible with legacy JSON format
- Simplified app launch logic (no more null checks for romPath/repoUrl)
- "Reset App" now fully clears all preferences, SQLite cache, and image cache
- New dependencies:
smb_connect,ftpconnect,share_plus - Provider reorganization:
config_providers.dart,game_providers.dart GameItemnow carriesproviderConfigfor authenticated downloads
- Console-style UI with full controller support
- Download queue with live progress and auto-extraction (ZIP/7z)
- Automatic box art via libretro-thumbnails
- Aggressive caching for large libraries (5000+ items)
- Instant search across all systems
- 17 supported systems (Nintendo, Sony, SEGA)