Skip to content

Add a Curated 4:3 filter to the Steam library - #1786

Open
Producdevity wants to merge 2 commits into
utkarshdalal:masterfrom
Producdevity:feat/4-3-filter-collection
Open

Add a Curated 4:3 filter to the Steam library#1786
Producdevity wants to merge 2 commits into
utkarshdalal:masterfrom
Producdevity:feat/4-3-filter-collection

Conversation

@Producdevity

@Producdevity Producdevity commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

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

  • All 24 related tests pass on both legacy and modern builds
  • Installed the modern debug build on an emulator and confirmed that the app starts normally

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
Screenshot 2026-08-01 at 17 19 11

Type of Change

  • Bug fix
  • Performance / stability improvement
  • Compatibility improvements
  • Other (requires prior approval)

Checklist

  • If I have access to #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.
  • This change aligns with the current project scope (core functionality, stability, or performance). If not, it has been explicitly approved beforehand.
  • I have attached a recording of the change. (I attached a screenshot)
  • I have read and agree to the contribution guidelines in 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

    • Added “Steam curated lists” to the options panel with selection, per-list counts, and a clear action.
    • Ships a bundled seed (app/src/main/assets/curated_lists/four_three_games.json, 364 app IDs) and refreshes via CuratedListRepository (24h TTL, recommended-only, complete results).
    • Filter behavior: selections within a section are OR; curated lists and Steam collections intersect; non-Steam sources are hidden when any Steam list filter is active.
  • Bug Fixes

    • Synced curated-list selection with the loaded lists (reconciles and persists via PrefManager) and keeps counts accurate.
    • Fixed cache loading to merge the bundled seed with only validated refreshes, with TTL/backoff, concurrency locking, and rejection of incomplete/invalid responses via FourThreeCuratorSource.

Written for commit 43be714. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added support for browsing Steam curated lists in the library.
    • Added the “Does it 4:3?” curated list with hundreds of recommended games.
    • Users can select, combine, and clear curated lists alongside existing collection filters.
    • Curated list selections and cached results are retained between sessions.
  • Improvements

    • Added loading, empty-state, and offline messaging.
    • Improved handling of unavailable or incomplete updates while preserving cached results.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Curated Steam list

Layer / File(s) Summary
Curated list data pipeline
app/src/main/assets/curated_lists/four_three_games.json, app/src/main/java/app/gamenative/steam/curated/*, app/src/main/java/app/gamenative/PrefManager.kt, app/src/test/java/app/gamenative/steam/curated/*
Defines the 4:3 list, loads bundled and cached IDs, refreshes paginated curator data, validates responses, and persists successful updates.
Library state and filtering
app/src/main/java/app/gamenative/ui/data/LibraryState.kt, app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt, app/src/main/java/app/gamenative/steam/SteamCollectionFilter.kt, app/src/test/java/app/gamenative/steam/SteamCollectionFilterTest.kt
Tracks curated lists and selections, combines curated and Steam collection filters, excludes non-Steam sources when filtering, and publishes counts.
Library curated-list controls
app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt, app/src/main/java/app/gamenative/ui/screen/library/components/LibraryOptionsPanel.kt, app/src/main/res/values/strings.xml
Adds curated-list callbacks and reusable selection UI with loading, empty, offline, count, clear, and localized list-name states.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • utkarshdalal/GameNative#1633: Extends the same Steam collection filtering, persisted preference, library state, view-model, and filter UI infrastructure.

Suggested reviewers: utkarshdalal

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
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.96% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a curated 4:3 filter to the Steam library.
Description check ✅ Passed The description explains the change, rationale, behavior, testing, screenshot, change type, and checklist status; only a recording is replaced by a screenshot.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 155ad56 and a4bbb1c.

📒 Files selected for processing (14)
  • app/src/main/assets/curated_lists/four_three_games.json
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/steam/SteamCollectionFilter.kt
  • app/src/main/java/app/gamenative/steam/curated/CuratedListDescriptor.kt
  • app/src/main/java/app/gamenative/steam/curated/CuratedListRepository.kt
  • app/src/main/java/app/gamenative/steam/curated/FourThreeCuratorSource.kt
  • app/src/main/java/app/gamenative/ui/data/LibraryState.kt
  • app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt
  • app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt
  • app/src/main/java/app/gamenative/ui/screen/library/components/LibraryOptionsPanel.kt
  • app/src/main/res/values/strings.xml
  • app/src/test/java/app/gamenative/steam/SteamCollectionFilterTest.kt
  • app/src/test/java/app/gamenative/steam/curated/CuratedListRepositoryTest.kt
  • app/src/test/java/app/gamenative/steam/curated/FourThreeCuratorSourceTest.kt

Comment thread app/src/main/java/app/gamenative/steam/curated/CuratedListRepository.kt Outdated
Comment thread app/src/main/java/app/gamenative/steam/curated/CuratedListRepository.kt Outdated
Comment thread app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt
Comment thread app/src/main/java/app/gamenative/steam/curated/CuratedListRepository.kt Outdated
Comment thread app/src/main/java/app/gamenative/steam/curated/CuratedListRepository.kt Outdated

@phobos665 phobos665 Aug 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth making this list for specifically steam (e.g. an entry called "steam" ).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, we could use a subdirectory or a prefix on the file name, what do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefix would work. We do similar with game patches IIRC

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a bunch of feedback here, will comment once they're addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on the size of the payload, i'd recommend cutting this down a bit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

@Producdevity
Producdevity requested a review from phobos665 August 7, 2026 07:32
@Producdevity

Copy link
Copy Markdown
Contributor Author

@utkarshdalal any updates on this review?

@utkarshdalal

Copy link
Copy Markdown
Owner

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?

@Producdevity

Copy link
Copy Markdown
Contributor Author

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?

@utkarshdalal

Good point, that makes sense. Could you add this account as a contributor to gamenative-update-worker or has the project been moved recently?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants