Skip to content

Re-enable local saves only with cloud sync safety nets#1146

Open
jeremybernstein wants to merge 4 commits intoutkarshdalal:masterfrom
jeremybernstein:jb/fix-local-saves-only
Open

Re-enable local saves only with cloud sync safety nets#1146
jeremybernstein wants to merge 4 commits intoutkarshdalal:masterfrom
jeremybernstein:jb/fix-local-saves-only

Conversation

@jeremybernstein
Copy link
Copy Markdown
Contributor

@jeremybernstein jeremybernstein commented Apr 8, 2026

Description

Re-enable "local saves only" with proper cloud sync safety nets.

Depends on #1169 (the sync cache fix).

  • Unhide "local saves only" toggle in GeneralTab
  • Show conflict dialog (Keep Remote / Keep Local) when force-syncing with local-saves-only enabled
  • Clear cloud sync cache on toggle-off so next sync detects divergence
  • pendingCloudResync flag distinguishes "cache cleared by toggle" from "cache cleared by app update" — the former gets generic conflict text, the latter gets upgrade-specific text
  • Skip cloud sync during verify-files when local-saves-only is active
  • Localized new strings across all supported languages

Closes #1145

Recording

Screenshot_20260408-100943

Test plan

  • Enable "local saves only" for a game, click Force Cloud Sync — conflict dialog appears
  • Choose "Keep Remote" — cloud saves download
  • Choose "Keep Local" — local saves upload
  • Disable "local saves only" — next force sync detects divergence correctly
  • Verify Files with local-saves-only on — no cloud sync triggered
  • Unit tests pass: ./gradlew testDebugUnitTest --tests "*.SteamAutoCloudTest"

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.
  • I have attached a recording of the change.
  • I have read and agree to the contribution guidelines in CONTRIBUTING.md.

Summary by CodeRabbit

  • New Features

    • Re-enabled per-game "Local saves only" toggle and added a conflict dialog to choose Keep Local or Keep Remote.
    • Centralized forced cloud-sync flow with clearer success/error feedback and explicit conflict prompts.
  • Behavior Changes

    • Toggling local-only clears related cloud-sync cache and updates a persisted pending-resync list to schedule or skip resyncs.
    • Conflict handling now updates pending-resync state and retries syncs based on user choice.
  • Localization

    • Added translations for the new flow across many languages.
  • Tests

    • Expanded sync-decision tests covering pending-resync and conflict messaging.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

Adds a per-game "local saves only" toggle and integrates it into cloud-sync flows: re-enables the UI switch, adds persisted pending-resync tracking, provides a Hilt entrypoint + helper to clear per-app cloud-cache, updates force-sync/conflict flows to surface choices for local-only cases, adjusts auto-cloud detection, expands localized strings, and adds tests.

Changes

Cohort / File(s) Summary
Service & DB access
app/src/main/java/app/gamenative/service/SteamService.kt
Added Hilt @EntryPoint (CloudSyncCacheEntryPoint) and clearCloudSyncCache(context, appId) to obtain FileChangeListsDao and delete per-app cache.
Preferences
app/src/main/java/app/gamenative/PrefManager.kt
Added persisted pendingCloudResync: Set<Int> stored as a comma-separated string to track games needing resync after toggling local-saves-only.
UI toggle
app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
Re-enabled the localSavesOnly SettingsSwitch bound to config.localSavesOnly.
App screen & sync flows
app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
Extracted performForceCloudSync(...); ForceCloudSync/VerifyFiles route through it; show DialogType.SYNC_CONFLICT for local-saves-only and handle user choice to rerun forced sync with chosen save location or show snackbars.
Container state handling
app/src/main/java/app/gamenative/utils/ContainerUtils.kt
When saved and localSavesOnly flips, call SteamService.clearCloudSyncCache(context, gameId) and add/remove gameId in PrefManager.pendingCloudResync.
Auto-cloud logic
app/src/main/java/app/gamenative/service/SteamAutoCloud.kt
Use PrefManager.pendingCloudResync to gate conflictUfsVersion assignment during uncached-local-files conflict detection; adjust has-local-changes handling.
App init conflict handling
app/src/main/java/app/gamenative/ui/PluviaMain.kt
Remove gameId from PrefManager.pendingCloudResync when constructing a SyncResult.Conflict dialog.
Tests
app/src/test/java/.../SteamAutoCloudTest.kt
Initialize PrefManager in tests, reset pendingCloudResync, and add tests asserting conflict behavior with/without pending resync and UFS-version expectations.
Localization
app/src/main/res/values.../strings.xml (multiple locales)
Added local_saves_only, local_saves_only_description, steam_verify_files_message_local_saves, library_cloud_sync_in_progress, and cloud_sync_local_saves_only_* strings across English and many locale files.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Screen as SteamAppScreen
    participant Dialog as SyncConflictDialog
    participant Service as SteamService
    participant DB as FileChangeListsDao
    participant Pref as PrefManager
    participant Cloud as Steam Cloud

    User->>Screen: Tap ForceCloudSync
    Screen->>Screen: Check container.isLocalSavesOnly

    alt local-saves-only
        Screen->>Dialog: Show SYNC_CONFLICT
        User->>Dialog: Choose Local or Remote
        Dialog->>Screen: Confirm(choice)
        Screen->>Service: performForceCloudSync(choice)
    else normal
        Screen->>Service: performForceCloudSync()
    end

    Service->>Pref: Check pendingCloudResync(gameId)
    Service->>DB: Optionally clear/read cache (deleteByAppId)
    Service->>Cloud: forceSyncUserFiles(prefixToPath, chosenLocation)
    Cloud->>Service: Return SyncResult (Success/UpToDate/Conflict)

    alt Conflict
        Service->>Screen: Show conflict dialog / open install dialog
        Service->>Pref: Remove gameId from pendingCloudResync (when dialog shown)
    else Success/UpToDate
        Service->>Screen: Show success/up-to-date snackbar
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I nibble through the cache and hop,

a toggle flips — no more cloudly mop,
conflicts knock, you choose which stays,
local or remote in careful ways,
resync wakes — hop on, hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% 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
Title check ✅ Passed The title clearly summarizes the main change: re-enabling the 'local saves only' feature with added safety mechanisms for cloud sync.
Description check ✅ Passed The PR description is comprehensive, covering changes, dependencies, test plan, and includes a recording. All required template sections are addressed.
Linked Issues check ✅ Passed All coding requirements from issue #1145 are met: unhide local-saves-only toggle, show conflict dialog on force-sync, clear cache on toggle-off, and skip cloud sync during verify-files.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the PR objectives: UI toggles, conflict dialog handling, cache management, and localization support for the local-saves-only feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

2 issues found across 19 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/service/SteamService.kt">

<violation number="1" location="app/src/main/java/app/gamenative/service/SteamService.kt:2638">
P2: Cloud-sync cache invalidation silently no-ops when `SteamService.instance` is null, which can skip the intended toggle-off cache reset path.</violation>

<violation number="2" location="app/src/main/java/app/gamenative/service/SteamService.kt:2639">
P2: `clearCloudSyncCache` is fire-and-forget, so cache deletion may not complete before subsequent sync reads stale cache state.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/app/gamenative/service/SteamService.kt`:
- Around line 2637-2644: The clearCloudSyncCache currently launches
deleteByAppId asynchronously so callers (e.g., ContainerUtils.applyToContainer)
may proceed to setLocalSavesOnly(false) before deletion finishes; change
clearCloudSyncCache to be awaitable: replace the fire-and-forget launch with a
suspend API (e.g., suspend fun clearCloudSyncCache(appId: Int)) or have it
return the Job/Deferred so the caller can await completion, call the DAO
deleteByAppId directly (or with withContext if needed) inside that suspend
function, and update the caller in ContainerUtils.applyToContainer to invoke and
await clearCloudSyncCache before calling setLocalSavesOnly(false).

In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`:
- Around line 1112-1126: The verify-files flow currently skips cloud sync when
container.isLocalSavesOnly but still shows the generic "upload to cloud first"
confirmation; update the confirmation shown in SteamAppScreen (the branch around
container.isLocalSavesOnly and SteamService.forceSyncUserFiles) to use a
different string when container.isLocalSavesOnly is true (e.g., a
local-saves-only-specific message or a generic "please back up your saves before
verifying" copy), add/replace the appropriate string resource, and ensure the UI
path that previously referenced the cloud-upload wording now conditionally
selects the new local-saves-only string.
- Around line 257-270: Replace the hardcoded SimpleDateFormat usage in the
SyncResult.Conflict branch with a locale-aware formatter: create a DateFormat
via DateFormat.getDateTimeInstance() and use it to format
syncResult.localTimestamp and syncResult.remoteTimestamp before constructing the
MessageDialogState for showInstallDialog; update the local variable currently
named fmt and ensure imports/reference to SimpleDateFormat are removed so the
dialog message shows user-localized date/time.

In `@app/src/main/java/app/gamenative/utils/ContainerUtils.kt`:
- Around line 474-478: The call site in applyToContainer currently calls
SteamService.clearCloudSyncCache(gameId) fire-and-forget, which may not finish
(or run at all if instance is null) before applyToContainer returns; change this
to awaitable, lifecycle-independent deletion by adding/using a synchronous or
suspend API on SteamService (e.g., clearCloudSyncCacheBlocking or suspend
clearCloudSyncCacheAwait) that ensures fileChangeListsDao.deleteByAppId(appId)
completes even if instance is not present, and call that from applyToContainer
so the delete finishes before returning; reference
SteamService.clearCloudSyncCache, SteamService.instance,
fileChangeListsDao.deleteByAppId, and the applyToContainer call site to
implement the blocking/suspend alternative and replace the fire-and-forget call.

In `@app/src/main/res/values-es/strings.xml`:
- Around line 1370-1371: Add the missing Spanish translations for the toggle
strings so the UI doesn't fall back to English: insert localized values for the
string resource names "local_saves_only" (setting label) and
"local_saves_only_description" (setting subtitle/description) into the
strings.xml file alongside the newly added conflict-dialog strings; use concise
Spanish copy matching tone of existing entries (e.g., "Solo guardados locales"
for the label and a short explanatory sentence for the description).
🪄 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

Run ID: 861220f9-7b06-4f24-a64b-16542271d4c5

📥 Commits

Reviewing files that changed from the base of the PR and between 24508c2 and 6163e8e.

📒 Files selected for processing (19)
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/java/app/gamenative/ui/enums/DialogType.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml

@jeremybernstein jeremybernstein force-pushed the jb/fix-local-saves-only branch from 6163e8e to d89e4f9 Compare April 8, 2026 08:33
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
app/src/main/java/app/gamenative/utils/ContainerUtils.kt (1)

474-478: ⚠️ Potential issue | 🟠 Major

Cache invalidation can still silently no-op when service is not running.

At Line 477, this depends on SteamService.clearCloudSyncCache(gameId), but that helper currently skips deletion when SteamService.instance is null. In that case, disabling local-saves-only won’t reliably force divergence detection on next sync.

Suggested direction
-        if (container.isLocalSavesOnly && !containerData.localSavesOnly) {
+        if (container.isLocalSavesOnly && !containerData.localSavesOnly) {
             val gameId = extractGameIdFromContainerId(container.id)
-            SteamService.clearCloudSyncCache(gameId)
+            SteamService.clearCloudSyncCacheBlocking(gameId) // lifecycle-independent, guaranteed delete
         }

And make the SteamService API lifecycle-independent (not tied to SteamService.instance availability) so this path is guaranteed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/utils/ContainerUtils.kt` around lines 474 -
478, The cache-clear call in ContainerUtils (when container.isLocalSavesOnly
changes) can silently no-op because SteamService.clearCloudSyncCache(gameId)
currently returns early if SteamService.instance is null; change the
SteamService API so clearing the cloud-sync cache does not depend on runtime
instance availability (either implement clearCloudSyncCache to perform deletion
against the persistent cache/storage directly when instance==null, or add a
lifecycle-independent method such as forceClearCloudSyncCache(gameId) and call
that from ContainerUtils after extractGameIdFromContainerId(container.id)) so
disabling local-saves-only always forces divergence detection on next sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/src/main/java/app/gamenative/utils/ContainerUtils.kt`:
- Around line 474-478: The cache-clear call in ContainerUtils (when
container.isLocalSavesOnly changes) can silently no-op because
SteamService.clearCloudSyncCache(gameId) currently returns early if
SteamService.instance is null; change the SteamService API so clearing the
cloud-sync cache does not depend on runtime instance availability (either
implement clearCloudSyncCache to perform deletion against the persistent
cache/storage directly when instance==null, or add a lifecycle-independent
method such as forceClearCloudSyncCache(gameId) and call that from
ContainerUtils after extractGameIdFromContainerId(container.id)) so disabling
local-saves-only always forces divergence detection on next sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6498522c-9707-405e-91ce-f3994f51773f

📥 Commits

Reviewing files that changed from the base of the PR and between 6163e8e and d89e4f9.

📒 Files selected for processing (19)
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/java/app/gamenative/ui/enums/DialogType.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/java/app/gamenative/ui/enums/DialogType.kt
🚧 Files skipped from review as they are similar to previous changes (14)
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ro/strings.xml

@jeremybernstein jeremybernstein force-pushed the jb/fix-local-saves-only branch from d89e4f9 to 7b47807 Compare April 8, 2026 08:44
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/app/gamenative/utils/ContainerUtils.kt`:
- Around line 475-478: Only clear the Steam cloud sync cache for real persisted
toggles off: wrap the existing condition in an additional check for the
persistence flag so it runs only when saveToDisk is true; i.e. change the logic
that uses container.isLocalSavesOnly and containerData.localSavesOnly in
ContainerUtils (the block calling SteamService.clearCloudSyncCache and
extractGameIdFromContainerId) to also require saveToDisk (or the actual
persistence parameter name) so temporary overrides (saveToDisk == false) do not
trigger cache invalidation.

In `@app/src/main/res/values-de/strings.xml`:
- Line 29: Update the German localized string identified by name
"steam_verify_files_message_local_saves": change the verb from "aktiviert ist"
to the correct plural form "aktiviert sind" so the message reads with proper
subject-verb agreement for "Speicherstände"; locate and edit the string value
for steam_verify_files_message_local_saves in the strings.xml entry and replace
the ending accordingly.
🪄 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

Run ID: 6c04acc0-c617-47f0-9392-446f6f2c3c99

📥 Commits

Reviewing files that changed from the base of the PR and between d89e4f9 and 7b47807.

📒 Files selected for processing (19)
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/java/app/gamenative/ui/enums/DialogType.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml
✅ Files skipped from review due to trivial changes (6)
  • app/src/main/java/app/gamenative/ui/enums/DialogType.kt
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/res/values-it/strings.xml
🚧 Files skipped from review as they are similar to previous changes (8)
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml

@jeremybernstein jeremybernstein force-pushed the jb/fix-local-saves-only branch from 7b47807 to f96ac80 Compare April 8, 2026 08:50
@jeremybernstein
Copy link
Copy Markdown
Contributor Author

added some unification of the dialogs in most recent force-push -- this add a bit of complexity for arguably a cleaner design.

@jeremybernstein jeremybernstein force-pushed the jb/fix-local-saves-only branch from f96ac80 to 6388a7b Compare April 8, 2026 11:48
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/app/gamenative/ui/PluviaMain.kt (1)

791-816: ⚠️ Potential issue | 🟠 Major

Replay the original launch flags when resolving SAVE_CONFLICT.

These callbacks re-enter preLaunchApp() with default options, so context from the original launch is lost. If this dialog was reached from an ignorePendingOperations = true path, the user can get bounced back into the pending-operations dialog; if it came from an external intent with useTemporaryOverride = true, the retry launches with the persisted container config instead of the temporary override. Please stash the original preLaunchApp() args and reuse them for both Keep Remote and Keep Local.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/ui/PluviaMain.kt` around lines 791 - 816,
The SAVE_CONFLICT handlers are re-calling preLaunchApp with fresh/default args
and losing original launch flags; capture the original preLaunchApp arguments
when the dialog is shown (e.g., into a local/remembered data structure) and
reuse them here, only overriding preferredSave to SaveLocation.Remote or
SaveLocation.Local; call preLaunchApp(originalArgs.copy(preferredSave = ...))
(preserving flags like ignorePendingOperations, useTemporaryOverride, persisted
container config, and the viewModel callbacks such as setLoadingDialogVisible,
setLoadingDialogProgress, setLoadingDialogMessage, setMessageDialogState, and
onSuccess = viewModel::launchApp), then clear msgDialogState as before.
🧹 Nitpick comments (1)
app/src/main/java/app/gamenative/PrefManager.kt (1)

481-488: Make pendingCloudResync updates atomic to avoid lost set mutations.

Call sites perform read-modify-write operations: ContainerUtils.kt:478, 482 (pendingCloudResync = pendingCloudResync ± gameId) and SteamAutoCloud.kt:838 (pendingCloudResync = pending - appInfo.id). Concurrent updates can race and clobber IDs. Wrap add/remove logic in dataStore.edit { } transactions.

Proposed refactor
-    var pendingCloudResync: Set<Int>
+    val pendingCloudResync: Set<Int>
         get() {
             val raw = getPref(PENDING_CLOUD_RESYNC, "")
             return if (raw.isEmpty()) emptySet() else raw.split(',').mapNotNull { it.toIntOrNull() }.toSet()
         }
-        set(value) {
-            setPref(PENDING_CLOUD_RESYNC, value.joinToString(","))
-        }
+
+    fun addPendingCloudResync(appId: Int) {
+        scope.launch {
+            dataStore.edit { pref ->
+                val current =
+                    pref[PENDING_CLOUD_RESYNC]
+                        ?.split(',')
+                        ?.mapNotNull { it.toIntOrNull() }
+                        ?.toMutableSet()
+                        ?: mutableSetOf()
+                current.add(appId)
+                pref[PENDING_CLOUD_RESYNC] = current.joinToString(",")
+            }
+        }
+    }
+
+    fun removePendingCloudResync(appId: Int) {
+        scope.launch {
+            dataStore.edit { pref ->
+                val current =
+                    pref[PENDING_CLOUD_RESYNC]
+                        ?.split(',')
+                        ?.mapNotNull { it.toIntOrNull() }
+                        ?.toMutableSet()
+                        ?: mutableSetOf()
+                current.remove(appId)
+                pref[PENDING_CLOUD_RESYNC] = current.joinToString(",")
+            }
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/PrefManager.kt` around lines 481 - 488, The
current read-modify-write use of the pendingCloudResync property can race and
lose updates; add atomic add/remove methods in PrefManager that perform the
mutation inside dataStore.edit { } (e.g., fun addPendingCloudResync(gameId: Int)
and fun removePendingCloudResync(gameId: Int)) where you read the current
PENDING_CLOUD_RESYNC value, modify the Set<Int>, and write the new comma‑joined
string back to the preference within the same transaction; then update callers
in ContainerUtils.kt and SteamAutoCloud.kt to call these new methods instead of
reading the property, modifying it, and reassigning it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/app/gamenative/service/SteamAutoCloud.kt`:
- Around line 832-840: The removal of appInfo.id from
PrefManager.pendingCloudResync in the isCacheCleared branch should be made
atomic and deferred until the resync actually succeeds: replace the
read-modify-write sequence around PrefManager.pendingCloudResync with an atomic
update (e.g., a synchronized/compare-and-set helper or add a
PrefManager.removePendingCloudResync(id):Boolean method) and move the call to
remove the id out of the early branch and into the success path of the
resync/confirmation flow (i.e., only after the conflict
resolution/upload/download completes successfully in the SteamAutoCloud resync
logic that sets conflictUfsVersion/CURRENT_UFS_PARSE_VERSION), ensuring
concurrent updates from other syncs or the toggle-off path are not lost.

In `@app/src/main/java/app/gamenative/utils/ContainerUtils.kt`:
- Around line 474-482: The Steam-specific cache and resync bookkeeping (calls to
SteamService.clearCloudSyncCache(...) and modifications of
PrefManager.pendingCloudResync) must be executed only for Steam containers; wrap
both branches that currently run those lines with a guard that checks the
container is a Steam container (e.g., container.isSteam or container.platform ==
ContainerPlatform.STEAM) before calling
extractGameIdFromContainerId(container.id),
SteamService.clearCloudSyncCache(context, gameId) or touching
PrefManager.pendingCloudResync, leaving non-Steam containers untouched.

In `@app/src/main/res/values-es/strings.xml`:
- Line 696: The string resource local_saves_only_description reads like an
unfinished action; change its Spanish text to an imperative sentence to match
surrounding subtitles (e.g., "Desactívala para mantener los guardados solo en el
dispositivo" or similar), updating the value of local_saves_only_description so
it reads as a complete command/imperative rather than an infinitive fragment.

---

Outside diff comments:
In `@app/src/main/java/app/gamenative/ui/PluviaMain.kt`:
- Around line 791-816: The SAVE_CONFLICT handlers are re-calling preLaunchApp
with fresh/default args and losing original launch flags; capture the original
preLaunchApp arguments when the dialog is shown (e.g., into a local/remembered
data structure) and reuse them here, only overriding preferredSave to
SaveLocation.Remote or SaveLocation.Local; call
preLaunchApp(originalArgs.copy(preferredSave = ...)) (preserving flags like
ignorePendingOperations, useTemporaryOverride, persisted container config, and
the viewModel callbacks such as setLoadingDialogVisible,
setLoadingDialogProgress, setLoadingDialogMessage, setMessageDialogState, and
onSuccess = viewModel::launchApp), then clear msgDialogState as before.

---

Nitpick comments:
In `@app/src/main/java/app/gamenative/PrefManager.kt`:
- Around line 481-488: The current read-modify-write use of the
pendingCloudResync property can race and lose updates; add atomic add/remove
methods in PrefManager that perform the mutation inside dataStore.edit { }
(e.g., fun addPendingCloudResync(gameId: Int) and fun
removePendingCloudResync(gameId: Int)) where you read the current
PENDING_CLOUD_RESYNC value, modify the Set<Int>, and write the new comma‑joined
string back to the preference within the same transaction; then update callers
in ContainerUtils.kt and SteamAutoCloud.kt to call these new methods instead of
reading the property, modifying it, and reassigning it.
🪄 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

Run ID: 27b8a54c-21a8-4519-be68-024a32d5ac69

📥 Commits

Reviewing files that changed from the base of the PR and between f96ac80 and 6388a7b.

📒 Files selected for processing (23)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/service/SteamAutoCloud.kt
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/java/app/gamenative/ui/PluviaMain.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/state/MessageDialogState.kt
  • app/src/main/java/app/gamenative/ui/enums/DialogType.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml
✅ Files skipped from review due to trivial changes (7)
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ru/strings.xml
🚧 Files skipped from review as they are similar to previous changes (7)
  • app/src/main/java/app/gamenative/ui/enums/DialogType.kt
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values-da/strings.xml
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt

@jeremybernstein jeremybernstein force-pushed the jb/fix-local-saves-only branch from 6388a7b to da9ec17 Compare April 8, 2026 12:08
Comment on lines -2037 to -2056
val localDate = Date(postSyncInfo.localTimestamp).toString()
val remoteDate = Date(postSyncInfo.remoteTimestamp).toString()
val (conflictTitle, conflictMessage) = postSyncInfo.conflictUfsVersion
?.let { v ->
val titleId = context.resources.getIdentifier(
"main_save_conflict_upgrade_v${v}_title", "string", context.packageName,
)
val msgId = context.resources.getIdentifier(
"main_save_conflict_upgrade_v${v}_message", "string", context.packageName,
)
if (titleId != 0 && msgId != 0) {
context.getString(titleId) to context.getString(msgId, localDate, remoteDate)
} else {
null
}
}
?: run {
context.getString(R.string.main_save_conflict_title) to
context.getString(R.string.main_save_conflict_message, localDate, remoteDate)
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

why is this removed?

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 was refactored to MessageDialogState.saveConflict to avoid duplication. @xXJSONDeruloXx requested that I unify the dialog usage, which is why those changes exist. I can back out to a previous version which is less invasive, but has some amount of dialog-related duplication. The actual sync conflict resolution shouldn't be affected at all.

@utkarshdalal
Copy link
Copy Markdown
Owner

This should not affect the existing sync conflict at all. Please separate it.

@jeremybernstein jeremybernstein force-pushed the jb/fix-local-saves-only branch from d4a1637 to b423966 Compare April 12, 2026 12:36
@jeremybernstein jeremybernstein changed the title Fix local saves only Re-enable local saves only with cloud sync safety nets Apr 12, 2026
@jeremybernstein
Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 12, 2026

✅ Actions performed

Reviews resumed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt (1)

2069-2128: Assert that pendingCloudResync is consumed, not just inspected.

These tests prove the wording branch, but they would still pass if the implementation regressed from “read-and-remove” to a plain membership check. Add a post-call assertion that steamAppId !in PrefManager.pendingCloudResync in the pending-resync scenarios so the one-shot behavior is locked down too.

Based on learnings, SteamAutoCloud.kt intentionally reads and removes appInfo.id from PrefManager.pendingCloudResync before conflict resolution because that one-shot classification drives the dialog wording.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt` around lines
2069 - 2128, The tests check wording branches but don't assert the one-shot flag
is consumed; after calling SteamAutoCloud.syncUserFiles in the pending-resync
test (function cacheCleared_pendingResync_conflictWithoutUpgradeText) add an
assertion that steamAppId is removed from PrefManager.pendingCloudResync (e.g.
assertFalse(steamAppId in PrefManager.pendingCloudResync) or equivalent) so the
test verifies the code both reads-and-removes the id; locate the similar
non-pending test cacheCleared_noPendingResync_conflictWithUpgradeText and do NOT
add the removal assertion there (only assert removal in the pending-resync
scenario).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/app/gamenative/service/SteamAutoCloud.kt`:
- Around line 826-835: The current logic treats any cache-absent-but-local-files
case (isCacheCleared) as an "upgrade" conflict and sets conflictUfsVersion,
which incorrectly surfaces upgrade wording for a first-offline-play; instead,
differentiate "cache never existed" from "cache was cleared by an upgrade" by
checking whether this app ever had a cloud cache before (e.g., a persisted
flag/lookup such as a new PrefManager.hasSeenCloudCache(appInfo.id) or existing
metadata) and only assign conflictUfsVersion = CURRENT_UFS_PARSE_VERSION when
cachePreviouslyExisted is true and appInfo.id is not in
PrefManager.pendingCloudResync; leave conflicts generic for first-run (cache
never existed) so users see non-upgrade wording.

In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`:
- Around line 256-269: The dialog currently hard-codes the generic timestamp
message for SyncResult.Conflict and loses the original conflict classification;
instead, read syncResult.conflictUfsVersion and build the MessageDialogState
using that classification (e.g., select the appropriate
title/message/confirm/dismiss strings based on conflictUfsVersion or pass the
classification into MessageDialogState) in the branch that calls
showInstallDialog(gameId, MessageDialogState(...)) so the dialog wording matches
the pending-resync vs app-update distinction (preserving the behavior intended
by SteamAutoCloud.kt).

---

Nitpick comments:
In `@app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt`:
- Around line 2069-2128: The tests check wording branches but don't assert the
one-shot flag is consumed; after calling SteamAutoCloud.syncUserFiles in the
pending-resync test (function
cacheCleared_pendingResync_conflictWithoutUpgradeText) add an assertion that
steamAppId is removed from PrefManager.pendingCloudResync (e.g.
assertFalse(steamAppId in PrefManager.pendingCloudResync) or equivalent) so the
test verifies the code both reads-and-removes the id; locate the similar
non-pending test cacheCleared_noPendingResync_conflictWithUpgradeText and do NOT
add the removal assertion there (only assert removal in the pending-resync
scenario).
🪄 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

Run ID: 7ee4ecc3-358c-4004-8de4-be02a2ea9453

📥 Commits

Reviewing files that changed from the base of the PR and between f96ac80 and b423966.

📒 Files selected for processing (22)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/service/SteamAutoCloud.kt
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/java/app/gamenative/ui/PluviaMain.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml
  • app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt
✅ Files skipped from review due to trivial changes (9)
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values-pl/strings.xml
🚧 Files skipped from review as they are similar to previous changes (8)
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/java/app/gamenative/ui/PluviaMain.kt
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-es/strings.xml

@jeremybernstein
Copy link
Copy Markdown
Contributor Author

PR has been rejiggered in terms of #1169 rather than the other way around.

@jeremybernstein jeremybernstein force-pushed the jb/fix-local-saves-only branch from b423966 to 515343e Compare April 13, 2026 12:35
@jeremybernstein
Copy link
Copy Markdown
Contributor Author

update to fix conflicts on current master (GOG sync). First four commits are from fix-cloud-overwrite-on-update PR (#1169)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt (1)

257-258: ⚠️ Potential issue | 🟡 Minor

Format the conflict timestamps with a locale-aware API.

Date.toString() still renders the raw JVM format here, so localized builds won't get user-region date/time formatting in this dialog.

💡 Suggested fix
-                        val localDate = Date(syncResult.localTimestamp).toString()
-                        val remoteDate = Date(syncResult.remoteTimestamp).toString()
+                        val formatter = java.text.DateFormat.getDateTimeInstance(
+                            java.text.DateFormat.MEDIUM,
+                            java.text.DateFormat.SHORT,
+                        )
+                        val localDate = formatter.format(Date(syncResult.localTimestamp))
+                        val remoteDate = formatter.format(Date(syncResult.remoteTimestamp))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`
around lines 257 - 258, The timestamps are being formatted with Date.toString()
which is not locale-aware; replace the usages that assign localDate and
remoteDate (in SteamAppScreen) to instead convert the epoch millis
(syncResult.localTimestamp / remoteTimestamp) to a java.time.Instant and format
via DateTimeFormatter.ofLocalizedDateTime(...) (or
DateTimeFormatter.ofLocalizedDate(...) as appropriate) with the current Locale
(e.g., Locale.getDefault() or the Compose LocalContext locale) so the displayed
times are localized for the user.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`:
- Around line 1178-1195: The SYNC_CONFLICT branches call hideInstallDialog(...)
and performForceCloudSync(..., SaveLocation.Remote/Local) but never clear the
pendingCloudResync marker; update both DialogType.SYNC_CONFLICT handlers (the
anonymous lambdas created in the primary click branch and in
effectiveDismissClick) to also consume/clear pendingCloudResync for the affected
id (use the same key used elsewhere, e.g., libraryItem.appId) after triggering
performForceCloudSync; mirror the cleanup approach used in PluviaMain.kt so the
pendingCloudResync entry is removed when the conflict resolution finishes.

---

Duplicate comments:
In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`:
- Around line 257-258: The timestamps are being formatted with Date.toString()
which is not locale-aware; replace the usages that assign localDate and
remoteDate (in SteamAppScreen) to instead convert the epoch millis
(syncResult.localTimestamp / remoteTimestamp) to a java.time.Instant and format
via DateTimeFormatter.ofLocalizedDateTime(...) (or
DateTimeFormatter.ofLocalizedDate(...) as appropriate) with the current Locale
(e.g., Locale.getDefault() or the Compose LocalContext locale) so the displayed
times are localized for the user.
🪄 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

Run ID: 5e489910-1b42-4497-9db0-bd337756c97f

📥 Commits

Reviewing files that changed from the base of the PR and between b423966 and 515343e.

📒 Files selected for processing (22)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/service/SteamAutoCloud.kt
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/java/app/gamenative/ui/PluviaMain.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml
  • app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt
✅ Files skipped from review due to trivial changes (8)
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-it/strings.xml
🚧 Files skipped from review as they are similar to previous changes (7)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-de/strings.xml

@jeremybernstein jeremybernstein force-pushed the jb/fix-local-saves-only branch from 515343e to 2f6424b Compare April 13, 2026 12:51
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt (1)

257-258: ⚠️ Potential issue | 🟡 Minor

Format the conflict timestamps with a locale-aware formatter.

Date.toString() renders the fixed JVM English format, so this regresses translated builds back to raw English timestamps inside the conflict dialog.

✏️ Suggested fix
-                        val localDate = Date(syncResult.localTimestamp).toString()
-                        val remoteDate = Date(syncResult.remoteTimestamp).toString()
+                        val formatter = java.text.DateFormat.getDateTimeInstance(
+                            java.text.DateFormat.MEDIUM,
+                            java.text.DateFormat.SHORT,
+                        )
+                        val localDate = formatter.format(Date(syncResult.localTimestamp))
+                        val remoteDate = formatter.format(Date(syncResult.remoteTimestamp))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`
around lines 257 - 258, The conflict dialog currently builds timestamps using
Date(syncResult.localTimestamp).toString() and
Date(syncResult.remoteTimestamp).toString() which produces JVM English-only
output; update SteamAppScreen.kt to format these timestamps with a locale-aware
formatter instead (e.g. use
java.time.Instant.ofEpochMilli(...).atZone(ZoneId.systemDefault()) and
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT/MEDIUM).withLocale(Locale.getDefault()),
or java.text.DateFormat.getDateTimeInstance(...).format(Date(...))) so localDate
and remoteDate are produced via the locale-aware formatter using
syncResult.localTimestamp and syncResult.remoteTimestamp.
🧹 Nitpick comments (3)
app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt (3)

2057-2105: Make the Keep Remote tests verify the replacement path.

Unlike the other new download fixtures, these two mocks drop /SaveGames from pathPrefixes and then only assert filesDownloaded > 0. A wrong-path download would still pass, so the tests do not prove that the conflicting local save was actually replaced.

Suggested tightening
-        val cloudFileChangeList = makeCloudFileChangeList(
-            cloudChangeNumber = localCn + 1,
-            files = listOf(mockFile),
-            pathPrefixes = listOf("%WinMyDocuments%/My Games/TestGame/Steam/76561198025127569"),
-        )
+        val cloudFileChangeList = makeCloudFileChangeList(
+            cloudChangeNumber = localCn + 1,
+            files = listOf(mockFile),
+            pathPrefixes = listOf("%WinMyDocuments%/My Games/TestGame/Steam/76561198025127569/SaveGames"),
+        )-        assertTrue("Should have downloaded files", result.filesDownloaded > 0)
+        assertTrue("Should have downloaded files", result.filesDownloaded > 0)
+        assertArrayEquals(
+            "Remote save should replace the conflicting local file",
+            cloudContent,
+            File(saveFilesDir, "SaveData_0.sav").readBytes(),
+        )
Apply the same tightening to both `preferRemote` scenarios.

Also applies to: 2125-2174

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt` around lines
2057 - 2105, The Keep Remote tests currently only assert result.filesDownloaded
> 0 while the cloud fixtures drop "/SaveGames" from pathPrefixes, so they don't
verify the actual replacement path; update the tests that call
SteamAutoCloud.syncUserFiles (both preferRemote scenarios around the
makeCloudFileChangeList calls) to assert that the downloaded file was written to
the expected local path (the path computed from the original pathPrefix
including the "/SaveGames" segment) or that the conflicting local save file was
overwritten/removed—locate the makeCloudFileChangeList invocation and the
testApp/local file setup and add an assertion checking the specific file path
content or existence to ensure the wrong-path download would fail the test.

2181-2207: Exercise the real one-shot pendingCloudResync path here.

Both tests delete the change-number row, so they currently look more like “never synced” than “cache cleared after toggle-off”. They also do not verify that the app ID is consumed from PrefManager.pendingCloudResync after the first classification.

Suggested tightening
 `@Test`
 fun cacheCleared_pendingResync_conflictWithoutUpgradeText() = runBlocking {
     db.appChangeNumbersDao().deleteByAppId(steamAppId)
     db.appFileChangeListsDao().deleteByAppId(steamAppId)
+    db.appChangeNumbersDao().insert(app.gamenative.data.ChangeNumbers(steamAppId, 5L))
     assertTrue("Precondition: local save files exist", saveFilesDir.listFiles()!!.isNotEmpty())

     // simulate local-saves toggle-off: appId in pendingCloudResync
     PrefManager.pendingCloudResync = setOf(steamAppId)
…
     assertNull(
         "conflictUfsVersion should be null — generic text, not upgrade",
         result.conflictUfsVersion,
     )
+    assertFalse(
+        "pendingCloudResync should be consumed after the first classification",
+        steamAppId in PrefManager.pendingCloudResync,
+    )
 }
Based on learnings, `clearCloudSyncCache(context, appId)` deletes the cache row before writing `PrefManager.pendingCloudResync`, and the `isCacheCleared` branch intentionally reads and removes that app ID before conflict resolution.

Also applies to: 2213-2240

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt` around lines
2181 - 2207, The test currently deletes the change-number row which makes it a
"never synced" case; instead simulate the real one-shot pendingCloudResync path
by not deleting the DB row and calling clearCloudSyncCache(context, steamAppId)
to model "cache cleared before pendingCloudResync is written", then set
PrefManager.pendingCloudResync = setOf(steamAppId) and call
SteamAutoCloud.syncUserFiles(...) as before; after the call assert the result is
Conflict and also assert that PrefManager.pendingCloudResync no longer contains
steamAppId (i.e., the appId was consumed/removed by the isCacheCleared branch).
Ensure the test references cacheCleared_pendingResync_conflictWithoutUpgradeText
(and the sibling test at lines ~2213-2240) and does not delete the change-number
row via db.appChangeNumbersDao().deleteByAppId.

1442-1452: Fail fast on unexpected prefixes in makePrefixToPath().

Returning tempDir from the default branch can turn a prefix-resolution regression into a harmless-looking “no files found” path. Throw here instead so these scenario tests fail at the real cause.

Suggested tightening
 private fun makePrefixToPath(): (String) -> String = { prefix ->
     // getPathTypePairs passes placeholders with % signs, production uses PathType.from()
     val stripped = prefix.removePrefix("%").removeSuffix("%")
     when (stripped) {
         "WinMyDocuments" -> {
             val imageFs = ImageFs.find(context)
             val wineprefix = File(imageFs.wineprefix)
             File(wineprefix, "dosdevices/c:/users/xuser/Documents").absolutePath
         }
-        else -> tempDir.absolutePath
+        else -> error("Unexpected prefix in SteamAutoCloudTest: $prefix")
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt` around lines
1442 - 1452, The current makePrefixToPath() silently returns tempDir for unknown
prefixes which hides regressions; update its default branch (after handling
"WinMyDocuments") to throw an exception (e.g., IllegalArgumentException or
IllegalStateException) that includes the original prefix/stripped value so tests
fail fast on unknown placeholders instead of returning tempDir; keep the
existing handling for "WinMyDocuments" and preserve the comment about %
placeholders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`:
- Around line 274-280: The code currently falls through to the generic error
case when SteamService.forceSyncUserFiles() returns SyncResult.InProgress, which
shows the raw "InProgress" text; add an explicit branch for
SyncResult.InProgress in the when handling inside SteamAppScreen (where
SnackbarManager.show is called) to call SnackbarManager.show with the
appropriate "sync already in progress" string resource (e.g.,
R.string.library_cloud_sync_already_in_progress) instead of the generic error
string so the user sees the correct human-readable message.

In `@app/src/main/res/values-ro/strings.xml`:
- Line 30: The Romanian string steam_verify_files_message_local_saves (and the
other affected Romanian string at the same file around the later occurrence)
uses an opening Romanian quote „ but a straight ASCII double-quote " as the
closer; update the closing quote to the proper Romanian closing quote ” so the
phrase “Doar salvări locale” uses matching „… ” punctuation (search for
steam_verify_files_message_local_saves and the other localized string around
lines ~1373-1374 to apply the fix).

---

Duplicate comments:
In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`:
- Around line 257-258: The conflict dialog currently builds timestamps using
Date(syncResult.localTimestamp).toString() and
Date(syncResult.remoteTimestamp).toString() which produces JVM English-only
output; update SteamAppScreen.kt to format these timestamps with a locale-aware
formatter instead (e.g. use
java.time.Instant.ofEpochMilli(...).atZone(ZoneId.systemDefault()) and
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT/MEDIUM).withLocale(Locale.getDefault()),
or java.text.DateFormat.getDateTimeInstance(...).format(Date(...))) so localDate
and remoteDate are produced via the locale-aware formatter using
syncResult.localTimestamp and syncResult.remoteTimestamp.

---

Nitpick comments:
In `@app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt`:
- Around line 2057-2105: The Keep Remote tests currently only assert
result.filesDownloaded > 0 while the cloud fixtures drop "/SaveGames" from
pathPrefixes, so they don't verify the actual replacement path; update the tests
that call SteamAutoCloud.syncUserFiles (both preferRemote scenarios around the
makeCloudFileChangeList calls) to assert that the downloaded file was written to
the expected local path (the path computed from the original pathPrefix
including the "/SaveGames" segment) or that the conflicting local save file was
overwritten/removed—locate the makeCloudFileChangeList invocation and the
testApp/local file setup and add an assertion checking the specific file path
content or existence to ensure the wrong-path download would fail the test.
- Around line 2181-2207: The test currently deletes the change-number row which
makes it a "never synced" case; instead simulate the real one-shot
pendingCloudResync path by not deleting the DB row and calling
clearCloudSyncCache(context, steamAppId) to model "cache cleared before
pendingCloudResync is written", then set PrefManager.pendingCloudResync =
setOf(steamAppId) and call SteamAutoCloud.syncUserFiles(...) as before; after
the call assert the result is Conflict and also assert that
PrefManager.pendingCloudResync no longer contains steamAppId (i.e., the appId
was consumed/removed by the isCacheCleared branch). Ensure the test references
cacheCleared_pendingResync_conflictWithoutUpgradeText (and the sibling test at
lines ~2213-2240) and does not delete the change-number row via
db.appChangeNumbersDao().deleteByAppId.
- Around line 1442-1452: The current makePrefixToPath() silently returns tempDir
for unknown prefixes which hides regressions; update its default branch (after
handling "WinMyDocuments") to throw an exception (e.g., IllegalArgumentException
or IllegalStateException) that includes the original prefix/stripped value so
tests fail fast on unknown placeholders instead of returning tempDir; keep the
existing handling for "WinMyDocuments" and preserve the comment about %
placeholders.
🪄 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

Run ID: 3f263e6d-9f88-48c1-bbe5-95d270d53993

📥 Commits

Reviewing files that changed from the base of the PR and between 515343e and 2f6424b.

📒 Files selected for processing (22)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/service/SteamAutoCloud.kt
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/java/app/gamenative/ui/PluviaMain.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml
  • app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt
✅ Files skipped from review due to trivial changes (7)
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
🚧 Files skipped from review as they are similar to previous changes (10)
  • app/src/main/java/app/gamenative/ui/PluviaMain.kt
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/java/app/gamenative/service/SteamAutoCloud.kt
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-uk/strings.xml

@jeremybernstein jeremybernstein force-pushed the jb/fix-local-saves-only branch from 2f6424b to d55787a Compare April 13, 2026 13:58
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt (1)

261-262: ⚠️ Potential issue | 🟡 Minor

Use a locale-aware formatter for the conflict timestamps.

Date.toString() will render JVM-style English timestamps even in translated builds, so this dialog regresses to non-localized date/time output.

Proposed fix
-                        val localDate = Date(syncResult.localTimestamp).toString()
-                        val remoteDate = Date(syncResult.remoteTimestamp).toString()
+                        val formatter = java.text.DateFormat.getDateTimeInstance(
+                            java.text.DateFormat.MEDIUM,
+                            java.text.DateFormat.SHORT,
+                            java.util.Locale.getDefault(),
+                        )
+                        val localDate = formatter.format(Date(syncResult.localTimestamp))
+                        val remoteDate = formatter.format(Date(syncResult.remoteTimestamp))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`
around lines 261 - 262, Replace the plain Date(...).toString() calls in
SteamAppScreen where localDate and remoteDate are computed so the timestamps are
formatted with the user's locale; specifically, use a locale-aware formatter
(e.g. java.text.DateFormat.getDateTimeInstance(...) with Locale.getDefault() or
Android's android.text.format.DateFormat.getDateFormat(context) combined with
getTimeFormat(context)) to format Date(syncResult.localTimestamp) and
Date(syncResult.remoteTimestamp) instead of calling toString(); update the code
that sets localDate and remoteDate in SteamAppScreen.kt to call the chosen
formatter with the Date objects so the dialog shows localized date/time.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`:
- Around line 261-262: Replace the plain Date(...).toString() calls in
SteamAppScreen where localDate and remoteDate are computed so the timestamps are
formatted with the user's locale; specifically, use a locale-aware formatter
(e.g. java.text.DateFormat.getDateTimeInstance(...) with Locale.getDefault() or
Android's android.text.format.DateFormat.getDateFormat(context) combined with
getTimeFormat(context)) to format Date(syncResult.localTimestamp) and
Date(syncResult.remoteTimestamp) instead of calling toString(); update the code
that sets localDate and remoteDate in SteamAppScreen.kt to call the chosen
formatter with the Date objects so the dialog shows localized date/time.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7fe40f73-68ac-4b33-859a-4b3f6da0801a

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6424b and d55787a.

📒 Files selected for processing (22)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/service/SteamAutoCloud.kt
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/java/app/gamenative/ui/PluviaMain.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml
  • app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt
✅ Files skipped from review due to trivial changes (2)
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/res/values-zh-rCN/strings.xml
🚧 Files skipped from review as they are similar to previous changes (13)
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-da/strings.xml
  • app/src/main/java/app/gamenative/service/SteamAutoCloud.kt
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt

@jeremybernstein jeremybernstein force-pushed the jb/fix-local-saves-only branch from d55787a to 8eeb7df Compare April 13, 2026 15:21
@jeremybernstein
Copy link
Copy Markdown
Contributor Author

rebased on master (where the base commit is now merged).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt (1)

1056-1061: ⚠️ Potential issue | 🟡 Minor

Consume pendingCloudResync on the onDismissRequest path too.

Line 1056 still routes DialogType.SYNC_CONFLICT through the generic hideInstallDialog(gameId) path, so back/outside dismissal bypasses the cleanup added on Lines 1188 and 1199. If MessageDialog is dismissible that way, the stale marker survives and the next cache-cleared conflict can be mislabeled. Either clear the marker in onDismissRequest as well, or make this dialog non-dismissible except via the explicit buttons.

💡 Possible fix
-            val onDismissRequest: (() -> Unit)? = {
-                hideInstallDialog(gameId)
-            }
+            val onDismissRequest: (() -> Unit)? = when (installDialogState.type) {
+                DialogType.SYNC_CONFLICT -> {
+                    {
+                        PrefManager.pendingCloudResync = PrefManager.pendingCloudResync - gameId
+                        hideInstallDialog(gameId)
+                    }
+                }
+                else -> {
+                    { hideInstallDialog(gameId) }
+                }
+            }

Based on learnings, in app/src/main/java/app/gamenative/service/SteamAutoCloud.kt, the presence of PrefManager.pendingCloudResync determines which conflict wording is shown, so the marker should be consumed on every exit path once this dialog has been surfaced.

#!/bin/bash
set -euo pipefail

echo "== SteamAppScreen conflict-dialog paths =="
sed -n '1050,1210p' app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt | nl -ba

echo
echo "== MessageDialog implementation =="
fd 'MessageDialog.kt' app/src/main/java -x sh -c '
  echo "-- $1 --"
  sed -n "1,240p" "$1" | nl -ba
' sh {}

Also applies to: 1185-1203

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`
around lines 1056 - 1061, The DialogType.SYNC_CONFLICT dismissal path currently
calls hideInstallDialog(gameId) in onDismissRequest/onDismissClick and thus can
bypass the cleanup that consumes PrefManager.pendingCloudResync; update the
onDismissRequest handler for the MessageDialog used for SYNC_CONFLICT so it also
clears/consumes pendingCloudResync (the same cleanup done around lines
1188/1199) before or instead of calling hideInstallDialog(gameId), or
alternatively make MessageDialog non-dismissible so only the explicit button
handlers (which already consume pendingCloudResync) can close it; reference the
onDismissRequest/onDismissClick handlers, hideInstallDialog(gameId),
MessageDialog, and PrefManager.pendingCloudResync to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`:
- Around line 1056-1061: The DialogType.SYNC_CONFLICT dismissal path currently
calls hideInstallDialog(gameId) in onDismissRequest/onDismissClick and thus can
bypass the cleanup that consumes PrefManager.pendingCloudResync; update the
onDismissRequest handler for the MessageDialog used for SYNC_CONFLICT so it also
clears/consumes pendingCloudResync (the same cleanup done around lines
1188/1199) before or instead of calling hideInstallDialog(gameId), or
alternatively make MessageDialog non-dismissible so only the explicit button
handlers (which already consume pendingCloudResync) can close it; reference the
onDismissRequest/onDismissClick handlers, hideInstallDialog(gameId),
MessageDialog, and PrefManager.pendingCloudResync to locate the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b28dc60-e920-408a-ae90-66258d802fed

📥 Commits

Reviewing files that changed from the base of the PR and between d55787a and 8eeb7df.

📒 Files selected for processing (22)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/service/SteamAutoCloud.kt
  • app/src/main/java/app/gamenative/service/SteamService.kt
  • app/src/main/java/app/gamenative/ui/PluviaMain.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml
  • app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt
✅ Files skipped from review due to trivial changes (7)
  • app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
🚧 Files skipped from review as they are similar to previous changes (7)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/java/app/gamenative/utils/ContainerUtils.kt

@jeremybernstein
Copy link
Copy Markdown
Contributor Author

@coderabbitai, the current code is correct: if the user backs out (maybe she wants to back up files manually or something before committing to a choice), the flag still needs to remain active -- a sync still needs to happen and a decision made. please stop bothering me about this.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

Tip

For best results, initiate chat on the files or code changes.

@jeremybernstein Understood, and apologies for the repeated noise. I've noted this for future reviews.


✏️ Learnings added
Learnt from: jeremybernstein
URL: https://github.com/utkarshdalal/GameNative/pull/1146

Timestamp: 2026-04-13T15:33:32.780Z
Learning: In `app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt` (PR `#1146`), when the user dismisses the `DialogType.SYNC_CONFLICT` dialog without choosing (i.e., backs out without pressing "Keep Local" or "Keep Remote"), the `pendingCloudResync` flag intentionally remains active. The user may want to back up files manually before committing to a choice, and a sync + decision still needs to happen. Do not flag the non-removal of the flag on back-out as a bug in future reviews.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 1146
File: app/src/main/java/app/gamenative/service/SteamAutoCloud.kt:832-840
Timestamp: 2026-04-08T12:05:56.079Z
Learning: In `app/src/main/java/app/gamenative/service/SteamAutoCloud.kt`, the `isCacheCleared` branch (around lines 832-840) intentionally reads and removes `appInfo.id` from `PrefManager.pendingCloudResync` at that point — before conflict resolution — because the presence/absence of the ID determines which dialog wording to display (upgrade-specific `conflictUfsVersion` vs generic local-saves-only text). This classification must happen pre-dialog. Do not flag this as needing to be deferred until after the resync succeeds in future reviews.

Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 1146
File: app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt:256-269
Timestamp: 2026-04-12T12:54:09.934Z
Learning: In `app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`, `performForceCloudSync` intentionally uses the generic timestamp-based conflict message (`main_save_conflict_message`) when `SyncResult.Conflict` is returned from a user-initiated force cloud sync action. The `conflictUfsVersion`/`pendingCloudResync` distinction (pending-resync vs app-update wording) is only relevant in the automatic pre-launch sync path (`PluviaMain.preLaunchApp`), not here. Do not flag the hardcoded generic conflict message in `performForceCloudSync` as missing the pending-resync classification in future reviews.

Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 1169
File: app/src/main/java/app/gamenative/PrefManager.kt:481-488
Timestamp: 2026-04-10T14:25:05.550Z
Learning: In `app/src/main/java/app/gamenative/PrefManager.kt`, `pendingCloudResync` (a `Set<Int>` backed by `PENDING_CLOUD_RESYNC` string preference) is only ever written from the main thread — specifically from the settings save path in `ContainerUtils.kt` and the conflict dialog dismiss path in `PluviaMain.kt`. The set holds at most 0–1 items at any given time. The non-atomic get+set pattern is intentional and safe; atomic `addPendingCloudResync`/`removePendingCloudResync` helpers are not justified. Do not flag the getter/setter as a race-condition or lost-update risk in future reviews.

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.

Local saves only — force sync overwrites without warning, toggle-off misses divergence

2 participants