Add a Curated 4:3 filter to the Steam library - #1786
Conversation
📝 WalkthroughWalkthroughAdds the “Does it 4:3?” curated Steam list. The change adds bundled data, remote refresh and caching, persisted selections, combined library filtering, and UI controls with localized states. ChangesCurated Steam list
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant LibraryViewModel
participant CuratedListRepository
participant FourThreeCuratorSource
participant LibraryOptionsPanel
LibraryViewModel->>CuratedListRepository: Observe curated lists
CuratedListRepository->>FourThreeCuratorSource: Refresh 4:3 recommendations
FourThreeCuratorSource-->>CuratedListRepository: Return validated app IDs
CuratedListRepository-->>LibraryViewModel: Publish curated list data
LibraryViewModel->>LibraryOptionsPanel: Provide lists, selections, and counts
LibraryOptionsPanel->>LibraryViewModel: Toggle or clear curated selections
LibraryViewModel-->>LibraryViewModel: Apply combined Steam filters
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/main/java/app/gamenative/steam/curated/CuratedListRepository.kt`:
- Around line 42-43: Remove the unconditional lastAttemptMs = 0L assignment from
loadFromCache so a previously recorded failed-refresh timestamp remains
available to isRefreshDue and preserves the retry backoff. Keep the existing
lastRefreshMs cache restoration unchanged, relying on the field’s process-start
default for initial state.
- Around line 39-40: Update the merge in the repository method containing
loadAllSeeds and decodeCache so cached lists override bundled seeds only when
cached.refreshedAtMs indicates a real refresh, not when it is 0. Preserve the
current seed-plus-cache behavior for refreshed caches and ensure an unrefreshed
or seed-derived cache cannot mask updated bundled lists.
In `@app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt`:
- Around line 280-297: In the curated-list reconciliation block, add the same
SnackbarManager.show notification used by the Steam-collections reconciliation
when recon.removedAny is true. Place it alongside the existing
PrefManager.libraryCuratedLists update, reusing the parallel block’s message and
behavior.
In
`@app/src/main/java/app/gamenative/ui/screen/library/components/LibraryOptionsPanel.kt`:
- Around line 351-366: Move the curated-list CollectionFilterSection, identified
by curatedLists and onCuratedListToggle, outside the isSteamConnected
conditional so it always renders and can show curated_lists_offline when
disconnected. Keep only the Steam collection section guarded by
isSteamConnected, preserving the existing curated-list data, selection, counts,
and callbacks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 22144ad3-0b21-4f3c-9263-2939919d31f9
📒 Files selected for processing (14)
app/src/main/assets/curated_lists/four_three_games.jsonapp/src/main/java/app/gamenative/PrefManager.ktapp/src/main/java/app/gamenative/steam/SteamCollectionFilter.ktapp/src/main/java/app/gamenative/steam/curated/CuratedListDescriptor.ktapp/src/main/java/app/gamenative/steam/curated/CuratedListRepository.ktapp/src/main/java/app/gamenative/steam/curated/FourThreeCuratorSource.ktapp/src/main/java/app/gamenative/ui/data/LibraryState.ktapp/src/main/java/app/gamenative/ui/model/LibraryViewModel.ktapp/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.ktapp/src/main/java/app/gamenative/ui/screen/library/components/LibraryOptionsPanel.ktapp/src/main/res/values/strings.xmlapp/src/test/java/app/gamenative/steam/SteamCollectionFilterTest.ktapp/src/test/java/app/gamenative/steam/curated/CuratedListRepositoryTest.ktapp/src/test/java/app/gamenative/steam/curated/FourThreeCuratorSourceTest.kt
There was a problem hiding this comment.
1 issue found across 14 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="app/src/main/java/app/gamenative/steam/curated/CuratedListRepository.kt">
<violation number="1" location="app/src/main/java/app/gamenative/steam/curated/CuratedListRepository.kt:68">
P2: Failed update checks are retried on every app restart once the last successful refresh is stale, rather than at most once per day. Persist and restore an attempt timestamp (or otherwise retain the failed-check throttle) before allowing another fetch.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| try { | ||
| val now = nowMs() | ||
| if (!isRefreshDue(now)) return | ||
| lastAttemptMs = now |
There was a problem hiding this comment.
P2: Failed update checks are retried on every app restart once the last successful refresh is stale, rather than at most once per day. Persist and restore an attempt timestamp (or otherwise retain the failed-check throttle) before allowing another fetch.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/steam/curated/CuratedListRepository.kt, line 68:
<comment>Failed update checks are retried on every app restart once the last successful refresh is stale, rather than at most once per day. Persist and restore an attempt timestamp (or otherwise retain the failed-check throttle) before allowing another fetch.</comment>
<file context>
@@ -0,0 +1,182 @@
+ try {
+ val now = nowMs()
+ if (!isRefreshDue(now)) return
+ lastAttemptMs = now
+
+ val fetched = try {
</file context>
There was a problem hiding this comment.
This is intentional, but do we want this behavior changed? @phobos665 @utkarshdalal
I am not behind my computer now but if I'm not mistaken I matched the behavior we had somewhere else (promoted game if I recall correctly).
There was a problem hiding this comment.
Might be worth making this list for specifically steam (e.g. an entry called "steam" ).
There was a problem hiding this comment.
Good call, we could use a subdirectory or a prefix on the file name, what do you think?
There was a problem hiding this comment.
prefix would work. We do similar with game patches IIRC
There was a problem hiding this comment.
There's a bunch of feedback here, will comment once they're addressed.
There was a problem hiding this comment.
feedback has been addressed
| private val endpoint = | ||
| "https://store.steampowered.com/curator/$CURATOR_CLAN_ID_4_3/ajaxgetfilteredrecommendations/".toHttpUrl() | ||
|
|
||
| private const val PAGE_SIZE = 1000 |
There was a problem hiding this comment.
Depending on the size of the payload, i'd recommend cutting this down a bit
There was a problem hiding this comment.
The current responsn is about 888KB for 364 entries.
My reasoning for doing it this way; A smaller page size would add additional requests that add room for error especially since this isn't a proper documented api/endpoint (and it doesn't reduce the total download size of course)
I don't think we should, but I am open to change it if there are reasons that haven't been considered yet
There was a problem hiding this comment.
That's not terrible, we can leave it as-is, although we'll wanna page it more if it grows substantially. We're pretty RAM hungry as-is
There was a problem hiding this comment.
Agreed, I'll keep an eye on this and any other potential other curated filters we may add in the feature. I did measured this and peak response memory is negligible and not worth the trade off imo. Thanks for pointing it out!
|
@utkarshdalal any updates on this review? |
|
Hi @Producdevity - I think it would be better if we have a cron job in cloudflare to get the list of 4:3 games and then return that to the user, instead of having all the users call an undocumented endpoint daily. The hardcoded json could also come from either github or R2. Thoughts? |
Good point, that makes sense. Could you add this account as a contributor to |
Description
Adds a Steam curated lists section below Steam collections, starting with Does it 4:3?.
I saw Russ from Retro Game Corps and a few other creators mention this during recent handheld streams and videos. With 4:3 devices like the Retroid Pocket Nova becoming more popular, having the curator list available as a library filter seemed useful.
Selecting multiple items within the same section includes games from any of those selections. Steam collections and curated lists narrow each other, so selecting “Favorites” and “Does it 4:3?” shows games that are in both. That felt like the least surprising behavior, but I haven’t been involved with the project for a while, so I may have misunderstood how these filters are expected to interact.
The app ships with a snapshot of the curator’s 364 recommended games, which means the filter works on first launch and while offline. It checks for an updated list when the saved data is more than 24 hours old. I used the same refresh interval as another data fetch already in the app. If the request fails or returns an incomplete list, the app keeps the last working copy.
Why this approach
Steam doesn’t have a proper API for curator lists. The only usable endpoint returns HTML inside a JSON response, so I avoided fetching it whenever the options panel opens for stability (since we have at least a stable json bundled) and to support this feature offline.
The app ships with a copy of the list and checks for updates at most once a day. When something goes wrong, we keep the current list. ( I also saw this approach in the app for something else)
We only include games the curator recommends, not informational or not-recommended posts.
(I only added the English strings)
This also leaves the door open to have any additional curated lists from steam as filters.
Testing
Have not tested any games since this doesn't touch any of that
Recording
It's a still UI addition (filter), if the screenshot isn't sufficient I will make a recording

Type of Change
Checklist
#code-changes, I have discussed this change there and it has been green-lighted. If I do not have access, I have still provided clear context in this PR. If I skip both, I accept that this change may face delays in review, may not be reviewed at all, or may be closed.CONTRIBUTING.md.Summary by cubic
Add a Steam curated filter to the library, starting with “Does it 4:3?”, so you can quickly find 4:3-friendly games. Works offline with a bundled list and refreshes daily when online. Includes fixes for selection sync and cache loading.
New Features
app/src/main/assets/curated_lists/four_three_games.json, 364 app IDs) and refreshes viaCuratedListRepository(24h TTL, recommended-only, complete results).Bug Fixes
PrefManager) and keeps counts accurate.FourThreeCuratorSource.Written for commit 43be714. Summary will update on new commits.
Summary by CodeRabbit
New Features
Improvements