From 0895a27ba52d62ca38940a74e7614e64f8f31dde Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 28 May 2026 11:05:25 +0000 Subject: [PATCH] fix(collections): preserve local data when remote sync payload is empty Commit 5a0b6237 removed the guard that skipped applying an empty remote collections JSON when the device already had local collections. That let pullFromServer wipe unsynced local collections and debounced push could persist the empty state to the server. Restore the preservation check with JsonNull-aware empty detection and add unit tests for isRemoteCollectionsEmpty. Co-authored-by: Fab --- .../collection/CollectionSyncService.kt | 21 ++++++++++++++++ .../collection/CollectionSyncServiceTest.kt | 25 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 composeApp/src/commonTest/kotlin/com/nuvio/app/features/collection/CollectionSyncServiceTest.kt diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionSyncService.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionSyncService.kt index e10467123..36f9cb62f 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionSyncService.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionSyncService.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put @@ -65,6 +66,16 @@ object CollectionSyncService { val remoteJson = remoteCollectionsJson.toString() val localJson = CollectionRepository.exportToJson() + if (isRemoteCollectionsEmpty(remoteCollectionsJson)) { + val currentCollections = CollectionRepository.collections.value + if (currentCollections.isNotEmpty()) { + log.i { + "pullFromServer — remote empty, preserving local ${currentCollections.size} collections" + } + return + } + } + if (remoteJson == localJson) { log.d { "pullFromServer — remote matches local, no update needed" } return @@ -132,3 +143,13 @@ object CollectionSyncService { } } } + +internal fun isRemoteCollectionsEmpty(remoteCollectionsJson: JsonElement): Boolean = + when (remoteCollectionsJson) { + is JsonArray -> remoteCollectionsJson.isEmpty() + is JsonNull -> true + else -> { + val serialized = remoteCollectionsJson.toString() + serialized == "[]" || serialized == "null" + } + } diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/collection/CollectionSyncServiceTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/collection/CollectionSyncServiceTest.kt new file mode 100644 index 000000000..93a1eb16c --- /dev/null +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/collection/CollectionSyncServiceTest.kt @@ -0,0 +1,25 @@ +package com.nuvio.app.features.collection + +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonPrimitive +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class CollectionSyncServiceTest { + @Test + fun emptyRemoteJsonArrayIsTreatedAsEmpty() { + assertTrue(isRemoteCollectionsEmpty(JsonArray(emptyList()))) + } + + @Test + fun nullRemoteJsonIsTreatedAsEmpty() { + assertTrue(isRemoteCollectionsEmpty(JsonNull)) + } + + @Test + fun nonEmptyRemoteJsonArrayIsNotEmpty() { + assertFalse(isRemoteCollectionsEmpty(JsonArray(listOf(JsonPrimitive("collection-id"))))) + } +}