feat: track Google Tasks local edits and sync conflicts #442

Closed
opened 2026-05-13 03:35:57 +00:00 by barrettruth · 0 comments
Owner

Parent: #144
Follow-up to: #291
Related: #290, #123

Problem

Google Tasks pull is implemented and idempotent by external link, but the current pull path is still effectively remote-wins for mapped fields. If an imported Google task is edited locally in Delta and Google later reports a different value, the next pull can overwrite the local edit without recording that Delta had diverged.

For v0.1, Delta should keep Google Tasks pull-only, but it needs enough sync state to avoid silent local data loss and to tell the operator when local changes, remote updates, or conflicts exist.

Definitions

Use sync metadata on the Google Tasks external link. Do not encode this in the Delta task status.

  • clean: the mapped local fields still match the last Google-applied snapshot.
  • local_modified: Delta changed one or more mapped fields after the last clean pull, while Google did not change those fields.
  • remote_outdated: Google has a newer value that Delta did not apply because the local field has changed since the last clean pull.
  • conflict: both Delta and Google changed the same mapped field since the last clean pull.

Mapped fields for this issue:

  • Google title <-> Delta description
  • Google notes <-> Delta notes
  • Google due <-> Delta due
  • Google completed/deleted <-> Delta status/completedAt
  • Google task list title <-> Delta category

Merge policy

Keep Google Tasks pull-only. Do not add writes back to Google Tasks here.

On pull, compare three values per mapped field:

  • last applied Google snapshot
  • current local Delta task value
  • incoming Google value

Rules:

  • If only Google changed, apply the incoming Google value.
  • If only Delta changed, keep the local value and mark the field local_modified.
  • If both changed to the same value, treat it as clean and update the snapshot.
  • If both changed differently, keep the local value and mark the field conflict.
  • If Google reports deletion for an already imported task and local mapped fields changed since last sync, do not silently cancel; mark a conflict/outdated deletion state instead.
  • If Google reports deletion for a task never imported, keep the current skip behavior.

Storage shape

Extend Google Tasks external link metadata with a last-applied snapshot and sync state. Exact field names can change during implementation, but it should be able to represent:

{
  syncState: "clean" | "local_modified" | "remote_outdated" | "conflict",
  changedFields: ["description", "due"],
  lastAppliedAt: "2026-05-13T00:00:00.000Z",
  lastRemoteUpdated: "2026-05-12T23:55:00.000Z",
  localUpdatedAt: "2026-05-13T00:03:00.000Z",
  appliedSnapshot: {
    description: "Buy milk",
    due: "2026-05-14",
    notes: null,
    status: "pending",
    category: "Errands"
  },
  remoteSnapshot: {
    description: "Buy oat milk",
    due: "2026-05-14",
    notes: null,
    status: "pending",
    category: "Errands"
  }
}

Existing provider metadata such as etag, updated, deleted, hidden, list IDs, and task IDs should remain available for idempotence and diagnostics.

Operator feedback

After a manual pull, the result should distinguish applied updates from protected local changes:

  • created
  • updated/applied
  • skipped/idempotent
  • keptLocal
  • conflicts
  • remoteOutdated
  • cancelled/deleted handling

Settings/status text should be concise, for example:

pulled 12, updated 4, kept 2 local edits, 1 conflict

The Google Tasks settings panel should expose aggregate sync issue counts without requiring a full conflict resolver in this issue.

Non-goals

  • Do not add bidirectional Google Tasks writes.
  • Do not add a large conflict resolution UI.
  • Do not change app login or multi-user auth behavior.
  • Do not block normal task usage when sync issues exist.

Acceptance criteria

  • Imported Google Tasks store a last-applied mapped-field snapshot.
  • Local edits to mapped fields after the last clean pull are detected.
  • Pull does not overwrite locally changed mapped fields without recording the decision.
  • Pull reports counts for kept local edits and conflicts/outdated remote changes.
  • Deleted/completed remote tasks are handled by the same merge policy instead of blindly overwriting local divergence.
  • Docs describe the v0.1 Google Tasks merge policy and explain that Tasks remains pull-only.
  • Tests cover remote-only update, local-only edit, both-sides same value, both-sides conflict, and remote deletion with local divergence.
Parent: #144 Follow-up to: #291 Related: #290, #123 ## Problem Google Tasks pull is implemented and idempotent by external link, but the current pull path is still effectively remote-wins for mapped fields. If an imported Google task is edited locally in Delta and Google later reports a different value, the next pull can overwrite the local edit without recording that Delta had diverged. For v0.1, Delta should keep Google Tasks pull-only, but it needs enough sync state to avoid silent local data loss and to tell the operator when local changes, remote updates, or conflicts exist. ## Definitions Use sync metadata on the Google Tasks external link. Do not encode this in the Delta task status. - `clean`: the mapped local fields still match the last Google-applied snapshot. - `local_modified`: Delta changed one or more mapped fields after the last clean pull, while Google did not change those fields. - `remote_outdated`: Google has a newer value that Delta did not apply because the local field has changed since the last clean pull. - `conflict`: both Delta and Google changed the same mapped field since the last clean pull. Mapped fields for this issue: - Google title <-> Delta description - Google notes <-> Delta notes - Google due <-> Delta due - Google completed/deleted <-> Delta status/completedAt - Google task list title <-> Delta category ## Merge policy Keep Google Tasks pull-only. Do not add writes back to Google Tasks here. On pull, compare three values per mapped field: - last applied Google snapshot - current local Delta task value - incoming Google value Rules: - If only Google changed, apply the incoming Google value. - If only Delta changed, keep the local value and mark the field `local_modified`. - If both changed to the same value, treat it as clean and update the snapshot. - If both changed differently, keep the local value and mark the field `conflict`. - If Google reports deletion for an already imported task and local mapped fields changed since last sync, do not silently cancel; mark a conflict/outdated deletion state instead. - If Google reports deletion for a task never imported, keep the current skip behavior. ## Storage shape Extend Google Tasks external link metadata with a last-applied snapshot and sync state. Exact field names can change during implementation, but it should be able to represent: ```ts { syncState: "clean" | "local_modified" | "remote_outdated" | "conflict", changedFields: ["description", "due"], lastAppliedAt: "2026-05-13T00:00:00.000Z", lastRemoteUpdated: "2026-05-12T23:55:00.000Z", localUpdatedAt: "2026-05-13T00:03:00.000Z", appliedSnapshot: { description: "Buy milk", due: "2026-05-14", notes: null, status: "pending", category: "Errands" }, remoteSnapshot: { description: "Buy oat milk", due: "2026-05-14", notes: null, status: "pending", category: "Errands" } } ``` Existing provider metadata such as `etag`, `updated`, `deleted`, `hidden`, list IDs, and task IDs should remain available for idempotence and diagnostics. ## Operator feedback After a manual pull, the result should distinguish applied updates from protected local changes: - created - updated/applied - skipped/idempotent - keptLocal - conflicts - remoteOutdated - cancelled/deleted handling Settings/status text should be concise, for example: ```text pulled 12, updated 4, kept 2 local edits, 1 conflict ``` The Google Tasks settings panel should expose aggregate sync issue counts without requiring a full conflict resolver in this issue. ## Non-goals - Do not add bidirectional Google Tasks writes. - Do not add a large conflict resolution UI. - Do not change app login or multi-user auth behavior. - Do not block normal task usage when sync issues exist. ## Acceptance criteria - Imported Google Tasks store a last-applied mapped-field snapshot. - Local edits to mapped fields after the last clean pull are detected. - Pull does not overwrite locally changed mapped fields without recording the decision. - Pull reports counts for kept local edits and conflicts/outdated remote changes. - Deleted/completed remote tasks are handled by the same merge policy instead of blindly overwriting local divergence. - Docs describe the v0.1 Google Tasks merge policy and explain that Tasks remains pull-only. - Tests cover remote-only update, local-only edit, both-sides same value, both-sides conflict, and remote deletion with local divergence.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
barrettruth/delta#442
No description provided.