Skip to content

fix(sync): reconcile the cache instead of rebuilding it - #236

Merged
romaintb merged 1 commit into
mainfrom
fix/stable-uuids-across-sync
Sep 5, 2026
Merged

romaintb merged 1 commit into
mainfrom
fix/stable-uuids-across-sync

Conversation

@romaintb

@romaintb romaintb commented Sep 5, 2026

Copy link
Copy Markdown
Owner

PR #209 item 1. Groundwork for keying UI state on local UUIDs.

The bug

store_snapshot deleted every row for the backend and then re-inserted with a fresh Uuid::new_v4(), so the on_conflict clauses in the store_*_batch functions never had anything to conflict with. Every local UUID churned on every sync, which since the move to a 5-minute timer means anything the UI keys on one goes stale under the user every five minutes.

The change

Delete only the rows the remote stopped returning (remote_id NOT IN the snapshot), and let the existing batches update the survivors in place. The unique indexes on (backend_uuid, remote_id) were already there, so the upserts just start working.

Two things fall out of it:

Task parent links are detached before the delete pass, alongside the project ones that already were. Todoist never returns completed tasks, so completing a parent makes it vanish from the fetch while its still-open subtasks keep coming. task.parent_uuid is ON DELETE CASCADE, so deleting the stale parent row would take a live subtask with it. The batches relink from the snapshot afterwards.

An empty section list no longer reconciles anything. sync (sync/mod.rs:258-268) turns a failed section fetch into an empty slice and carries on, so a backend hiccup used to wipe every cached section. The other three fetches return early on failure, so empty honestly means empty there and they reconcile unguarded.

No entity definition changed, so SCHEMA_VERSION stays put.

Tests

Two new ones, both verified to fail against the old wipe-and-reinsert:

  • snapshot_keeps_uuids_and_orphans_subtasks_of_vanished_parents covers both halves: a renamed project keeps its UUID across a sync, and a subtask survives its parent being completed away with parent_uuid cleared.
  • empty_section_list_leaves_cached_sections_alone pins the ambiguous-empty branch.

The two existing snapshot tests are unchanged in intent, just moved onto a shared test_service helper since the third would have been a third copy of the same 25 lines of setup.

cargo fmt, cargo clippy --all-targets, cargo test all clean.

Next

This unblocks the rest of the family, none of it in this PR:

store_snapshot deleted every row for the backend and re-inserted with a fresh
Uuid::new_v4(), so the on_conflict clauses in the store_*_batch functions never
fired and every local UUID churned on each sync. Anything the UI keys on one
goes stale under the user, now on a 5-minute timer.

Delete only the rows the remote stopped returning, and let the batches update
the survivors in place.

Two things fall out of that:

Task parent links are detached before the delete pass, alongside the project
ones that already were. Todoist never returns completed tasks, so completing a
parent makes it vanish from the fetch while its still-open subtasks keep
coming. The task hierarchy FK cascades, so deleting the stale parent row would
take a live subtask with it.

An empty section list no longer reconciles anything. sync turns a failed
section fetch into an empty slice and carries on, so a backend hiccup used to
wipe every cached section. The other three fetches abort the sync on failure,
where empty honestly means empty.
@romaintb
romaintb merged commit 2be8c29 into main Sep 5, 2026
9 checks passed
@romaintb
romaintb deleted the fix/stable-uuids-across-sync branch September 5, 2026 15:38
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.

1 participant