diff --git a/lua/pending/sync/gcal.lua b/lua/pending/sync/gcal.lua index f90d7c1..99b9e76 100644 --- a/lua/pending/sync/gcal.lua +++ b/lua/pending/sync/gcal.lua @@ -97,7 +97,6 @@ local function update_event(access_token, calendar_id, event_id, task) summary = task.description, start = { date = task.due }, ['end'] = { date = next_day(task.due or '') }, - transparency = 'transparent', } local _, err = oauth.curl_request( 'PATCH', diff --git a/lua/pending/sync/gtasks.lua b/lua/pending/sync/gtasks.lua index 9fc7459..d747c51 100644 --- a/lua/pending/sync/gtasks.lua +++ b/lua/pending/sync/gtasks.lua @@ -231,9 +231,8 @@ end ---@return integer created ---@return integer updated ---@return integer deleted ----@return integer failed local function push_pass(access_token, tasklists, s, now_ts, by_gtasks_id) - local created, updated, deleted, failed = 0, 0, 0, 0 + local created, updated, deleted = 0, 0, 0 for _, task in ipairs(s:tasks()) do local extra = task._extra or {} local gtid = extra['_gtasks_task_id'] --[[@as string?]] @@ -243,14 +242,12 @@ local function push_pass(access_token, tasklists, s, now_ts, by_gtasks_id) local err = delete_gtask(access_token, list_id, gtid) if err then log.warn('gtasks delete failed: ' .. err) - failed = failed + 1 else if not task._extra then task._extra = {} end task._extra['_gtasks_task_id'] = nil task._extra['_gtasks_list_id'] = nil - task._extra['_gtasks_synced_at'] = nil if next(task._extra) == nil then task._extra = nil end @@ -259,17 +256,11 @@ local function push_pass(access_token, tasklists, s, now_ts, by_gtasks_id) end elseif task.status ~= 'deleted' then if gtid and list_id then - local synced_at = extra['_gtasks_synced_at'] --[[@as string?]] - if not synced_at or task.modified > synced_at then - local err = update_gtask(access_token, list_id, gtid, task_to_gtask(task)) - if err then - log.warn('gtasks update failed: ' .. err) - failed = failed + 1 - else - task._extra = task._extra or {} - task._extra['_gtasks_synced_at'] = now_ts - updated = updated + 1 - end + local err = update_gtask(access_token, list_id, gtid, task_to_gtask(task)) + if err then + log.warn('gtasks update failed: ' .. err) + else + updated = updated + 1 end elseif task.status == 'pending' then local cat = task.category or config.get().default_category @@ -278,14 +269,12 @@ local function push_pass(access_token, tasklists, s, now_ts, by_gtasks_id) local new_id, create_err = create_gtask(access_token, lid, task_to_gtask(task)) if create_err then log.warn('gtasks create failed: ' .. create_err) - failed = failed + 1 elseif new_id then if not task._extra then task._extra = {} end task._extra['_gtasks_task_id'] = new_id task._extra['_gtasks_list_id'] = lid - task._extra['_gtasks_synced_at'] = now_ts task.modified = now_ts by_gtasks_id[new_id] = task created = created + 1 @@ -294,7 +283,7 @@ local function push_pass(access_token, tasklists, s, now_ts, by_gtasks_id) end end end - return created, updated, deleted, failed + return created, updated, deleted end ---@param access_token string @@ -304,24 +293,14 @@ end ---@param by_gtasks_id table ---@return integer created ---@return integer updated ----@return integer failed ----@return table seen_remote_ids ----@return table fetched_list_ids local function pull_pass(access_token, tasklists, s, now_ts, by_gtasks_id) - local created, updated, failed = 0, 0, 0 - ---@type table - local seen_remote_ids = {} - ---@type table - local fetched_list_ids = {} + local created, updated = 0, 0 for list_name, list_id in pairs(tasklists) do local items, err = list_gtasks(access_token, list_id) if err then log.warn('error fetching list ' .. list_name .. ': ' .. err) - failed = failed + 1 else - fetched_list_ids[list_id] = true for _, gtask in ipairs(items or {}) do - seen_remote_ids[gtask.id] = true local local_task = by_gtasks_id[gtask.id] if local_task then local gtask_updated = gtask.updated or '' @@ -331,8 +310,6 @@ local function pull_pass(access_token, tasklists, s, now_ts, by_gtasks_id) for k, v in pairs(fields) do local_task[k] = v end - local_task._extra = local_task._extra or {} - local_task._extra['_gtasks_synced_at'] = now_ts local_task.modified = now_ts updated = updated + 1 end @@ -341,7 +318,6 @@ local function pull_pass(access_token, tasklists, s, now_ts, by_gtasks_id) fields._extra = { _gtasks_task_id = gtask.id, _gtasks_list_id = list_id, - _gtasks_synced_at = now_ts, } local new_task = s:add(fields) by_gtasks_id[gtask.id] = new_task @@ -350,38 +326,7 @@ local function pull_pass(access_token, tasklists, s, now_ts, by_gtasks_id) end end end - return created, updated, failed, seen_remote_ids, fetched_list_ids -end - ----@param s pending.Store ----@param seen_remote_ids table ----@param fetched_list_ids table ----@param now_ts string ----@return integer unlinked -local function detect_remote_deletions(s, seen_remote_ids, fetched_list_ids, now_ts) - local unlinked = 0 - for _, task in ipairs(s:tasks()) do - local extra = task._extra or {} - local gtid = extra['_gtasks_task_id'] - local list_id = extra['_gtasks_list_id'] - if - task.status ~= 'deleted' - and gtid - and list_id - and fetched_list_ids[list_id] - and not seen_remote_ids[gtid] - then - task._extra['_gtasks_task_id'] = nil - task._extra['_gtasks_list_id'] = nil - task._extra['_gtasks_synced_at'] = nil - if next(task._extra) == nil then - task._extra = nil - end - task.modified = now_ts - unlinked = unlinked + 1 - end - end - return unlinked + return created, updated end ---@param access_token string @@ -429,17 +374,14 @@ function M.push() ---@cast s pending.Store ---@cast now_ts string local by_gtasks_id = build_id_index(s) - local created, updated, deleted, failed = - push_pass(access_token, tasklists, s, now_ts, by_gtasks_id) + local created, updated, deleted = push_pass(access_token, tasklists, s, now_ts, by_gtasks_id) s:save() require('pending')._recompute_counts() local buffer = require('pending.buffer') if buffer.bufnr() and vim.api.nvim_buf_is_valid(buffer.bufnr()) then buffer.render(buffer.bufnr()) end - log.info( - string.format('Google Tasks pushed — +%d ~%d -%d !%d', created, updated, deleted, failed) - ) + log.info(string.format('Google Tasks pushed — +%d ~%d -%d', created, updated, deleted)) end) end @@ -452,24 +394,14 @@ function M.pull() ---@cast s pending.Store ---@cast now_ts string local by_gtasks_id = build_id_index(s) - local created, updated, failed, seen_remote_ids, fetched_list_ids = - pull_pass(access_token, tasklists, s, now_ts, by_gtasks_id) - local unlinked = detect_remote_deletions(s, seen_remote_ids, fetched_list_ids, now_ts) + local created, updated = pull_pass(access_token, tasklists, s, now_ts, by_gtasks_id) s:save() require('pending')._recompute_counts() local buffer = require('pending.buffer') if buffer.bufnr() and vim.api.nvim_buf_is_valid(buffer.bufnr()) then buffer.render(buffer.bufnr()) end - log.info( - string.format( - 'Google Tasks pulled — +%d ~%d !%d, unlinked: %d', - created, - updated, - failed, - unlinked - ) - ) + log.info(string.format('Google Tasks pulled — +%d ~%d', created, updated)) end) end @@ -482,11 +414,9 @@ function M.sync() ---@cast s pending.Store ---@cast now_ts string local by_gtasks_id = build_id_index(s) - local pushed_create, pushed_update, pushed_delete, pushed_failed = + local pushed_create, pushed_update, pushed_delete = push_pass(access_token, tasklists, s, now_ts, by_gtasks_id) - local pulled_create, pulled_update, pulled_failed, seen_remote_ids, fetched_list_ids = - pull_pass(access_token, tasklists, s, now_ts, by_gtasks_id) - local unlinked = detect_remote_deletions(s, seen_remote_ids, fetched_list_ids, now_ts) + local pulled_create, pulled_update = pull_pass(access_token, tasklists, s, now_ts, by_gtasks_id) s:save() require('pending')._recompute_counts() local buffer = require('pending.buffer') @@ -495,15 +425,12 @@ function M.sync() end log.info( string.format( - 'Google Tasks synced — push: +%d ~%d -%d !%d, pull: +%d ~%d !%d, unlinked: %d', + 'Google Tasks synced — push: +%d ~%d -%d, pull: +%d ~%d', pushed_create, pushed_update, pushed_delete, - pushed_failed, pulled_create, - pulled_update, - pulled_failed, - unlinked + pulled_update ) ) end) @@ -515,9 +442,6 @@ M._build_notes = build_notes M._parse_notes = parse_notes M._task_to_gtask = task_to_gtask M._gtask_to_fields = gtask_to_fields -M._push_pass = push_pass -M._pull_pass = pull_pass -M._detect_remote_deletions = detect_remote_deletions ---@return nil function M.health() diff --git a/spec/gtasks_spec.lua b/spec/gtasks_spec.lua index 1e0f7ef..19328d9 100644 --- a/spec/gtasks_spec.lua +++ b/spec/gtasks_spec.lua @@ -176,193 +176,3 @@ describe('gtasks field conversion', function() end) end) end) - -describe('gtasks push_pass _gtasks_synced_at', function() - local helpers = require('spec.helpers') - local store_mod = require('pending.store') - local oauth = require('pending.sync.oauth') - local s - local orig_curl - - before_each(function() - local dir = helpers.tmpdir() - s = store_mod.new(dir .. '/pending.json') - s:load() - orig_curl = oauth.curl_request - end) - - after_each(function() - oauth.curl_request = orig_curl - end) - - it('sets _gtasks_synced_at after push create', function() - local task = - s:add({ description = 'New task', status = 'pending', category = 'Work', priority = 0 }) - - oauth.curl_request = function(method, url, _headers, _body) - if method == 'POST' and url:find('/tasks$') then - return { id = 'gtask-new-1' }, nil - end - return {}, nil - end - - local now_ts = '2026-03-05T10:00:00Z' - local tasklists = { Work = 'list-1' } - local by_id = {} - gtasks._push_pass('fake-token', tasklists, s, now_ts, by_id) - - assert.is_not_nil(task._extra) - assert.equals('2026-03-05T10:00:00Z', task._extra['_gtasks_synced_at']) - end) - - it('skips update when modified <= _gtasks_synced_at', function() - local task = - s:add({ description = 'Existing task', status = 'pending', category = 'Work', priority = 0 }) - task._extra = { - _gtasks_task_id = 'remote-1', - _gtasks_list_id = 'list-1', - _gtasks_synced_at = '2026-03-05T10:00:00Z', - } - task.modified = '2026-03-05T09:00:00Z' - - local patch_called = false - oauth.curl_request = function(method, _url, _headers, _body) - if method == 'PATCH' then - patch_called = true - end - return {}, nil - end - - local now_ts = '2026-03-05T11:00:00Z' - local tasklists = { Work = 'list-1' } - local by_id = { ['remote-1'] = task } - gtasks._push_pass('fake-token', tasklists, s, now_ts, by_id) - - assert.is_false(patch_called) - end) - - it('pushes update when modified > _gtasks_synced_at', function() - local task = - s:add({ description = 'Changed task', status = 'pending', category = 'Work', priority = 0 }) - task._extra = { - _gtasks_task_id = 'remote-2', - _gtasks_list_id = 'list-1', - _gtasks_synced_at = '2026-03-05T08:00:00Z', - } - task.modified = '2026-03-05T09:00:00Z' - - local patch_called = false - oauth.curl_request = function(method, _url, _headers, _body) - if method == 'PATCH' then - patch_called = true - end - return {}, nil - end - - local now_ts = '2026-03-05T11:00:00Z' - local tasklists = { Work = 'list-1' } - local by_id = { ['remote-2'] = task } - gtasks._push_pass('fake-token', tasklists, s, now_ts, by_id) - - assert.is_true(patch_called) - end) - - it('pushes update when no _gtasks_synced_at (backwards compat)', function() - local task = - s:add({ description = 'Old task', status = 'pending', category = 'Work', priority = 0 }) - task._extra = { - _gtasks_task_id = 'remote-3', - _gtasks_list_id = 'list-1', - } - task.modified = '2026-01-01T00:00:00Z' - - local patch_called = false - oauth.curl_request = function(method, _url, _headers, _body) - if method == 'PATCH' then - patch_called = true - end - return {}, nil - end - - local now_ts = '2026-03-05T11:00:00Z' - local tasklists = { Work = 'list-1' } - local by_id = { ['remote-3'] = task } - gtasks._push_pass('fake-token', tasklists, s, now_ts, by_id) - - assert.is_true(patch_called) - end) -end) - -describe('gtasks detect_remote_deletions', function() - local helpers = require('spec.helpers') - local store_mod = require('pending.store') - local s - - before_each(function() - local dir = helpers.tmpdir() - s = store_mod.new(dir .. '/pending.json') - s:load() - end) - - it('clears remote IDs when list was fetched but task ID is absent', function() - local task = - s:add({ description = 'Gone remote', status = 'pending', category = 'Work', priority = 0 }) - task._extra = { - _gtasks_task_id = 'old-remote-id', - _gtasks_list_id = 'list-1', - _gtasks_synced_at = '2026-01-01T00:00:00Z', - } - - local seen = {} - local fetched = { ['list-1'] = true } - local now_ts = '2026-03-05T10:00:00Z' - - local unlinked = gtasks._detect_remote_deletions(s, seen, fetched, now_ts) - - assert.equals(1, unlinked) - assert.is_nil(task._extra) - assert.equals('2026-03-05T10:00:00Z', task.modified) - end) - - it('leaves task untouched when its list fetch failed', function() - local task = s:add({ - description = 'Unknown list task', - status = 'pending', - category = 'Work', - priority = 0, - }) - task._extra = { - _gtasks_task_id = 'remote-id', - _gtasks_list_id = 'list-unfetched', - } - - local seen = {} - local fetched = {} - local now_ts = '2026-03-05T10:00:00Z' - - local unlinked = gtasks._detect_remote_deletions(s, seen, fetched, now_ts) - - assert.equals(0, unlinked) - assert.is_not_nil(task._extra) - assert.equals('remote-id', task._extra['_gtasks_task_id']) - end) - - it('skips tasks with status == deleted', function() - local task = - s:add({ description = 'Deleted task', status = 'deleted', category = 'Work', priority = 0 }) - task._extra = { - _gtasks_task_id = 'remote-del', - _gtasks_list_id = 'list-1', - } - - local seen = {} - local fetched = { ['list-1'] = true } - local now_ts = '2026-03-05T10:00:00Z' - - local unlinked = gtasks._detect_remote_deletions(s, seen, fetched, now_ts) - - assert.equals(0, unlinked) - assert.is_not_nil(task._extra) - assert.equals('remote-del', task._extra['_gtasks_task_id']) - end) -end)