docs: update vimdoc for sync refactor, remove demo scripts

Problem: Docs still referenced openssl dependency, defaulting to `sync`
action, and the `calendar` config field. Demo scripts used the old
singleton `store` API.

Solution: Update vimdoc and README to reflect explicit actions, per-
category calendars, and pure-Lua PKCE. Remove stale demo scripts and
update sync specs to match new behavior.
This commit is contained in:
Barrett Ruth 2026-03-05 11:24:43 -05:00
parent 9a762a5320
commit bc2a106617
6 changed files with 42 additions and 110 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
doc/tags doc/tags
*.log *.log
minimal_init.lua
.*cache* .*cache*
CLAUDE.md CLAUDE.md

View file

@ -7,7 +7,7 @@ Edit tasks like text. `:w` saves them.
## Requirements ## Requirements
- Neovim 0.10+ - Neovim 0.10+
- (Optionally) `curl` and `openssl` for Google Calendar and Google Task sync - (Optionally) `curl` for Google Calendar and Google Task sync
## Installation ## Installation

View file

@ -73,7 +73,7 @@ REQUIREMENTS *pending-requirements*
- Neovim 0.10+ - Neovim 0.10+
- No external dependencies for local use - No external dependencies for local use
- `curl` and `openssl` are required for the `gcal` and `gtasks` sync backends - `curl` is required for the `gcal` and `gtasks` sync backends
============================================================================== ==============================================================================
INSTALL *pending-install* INSTALL *pending-install*
@ -149,17 +149,17 @@ COMMANDS *pending-commands*
Open the list with |:copen| to navigate to each task's category. Open the list with |:copen| to navigate to each task's category.
*:Pending-gtasks* *:Pending-gtasks*
:Pending gtasks [{action}] :Pending gtasks {action}
Run a Google Tasks sync action. {action} defaults to `sync` when omitted. Run a Google Tasks action. An explicit action is required.
Actions: ~ Actions: ~
`sync` Push local changes then pull remote changes (default). `sync` Push local changes then pull remote changes.
`push` Push local changes to Google Tasks only. `push` Push local changes to Google Tasks only.
`pull` Pull remote changes from Google Tasks only. `pull` Pull remote changes from Google Tasks only.
`auth` Run the OAuth authorization flow. `auth` Run the OAuth authorization flow.
Examples: >vim Examples: >vim
:Pending gtasks " push then pull (default) :Pending gtasks sync " push then pull
:Pending gtasks push " push local → Google Tasks :Pending gtasks push " push local → Google Tasks
:Pending gtasks pull " pull Google Tasks → local :Pending gtasks pull " pull Google Tasks → local
:Pending gtasks auth " authorize :Pending gtasks auth " authorize
@ -169,16 +169,15 @@ COMMANDS *pending-commands*
See |pending-gtasks| for full details. See |pending-gtasks| for full details.
*:Pending-gcal* *:Pending-gcal*
:Pending gcal [{action}] :Pending gcal {action}
Run a Google Calendar sync action. {action} defaults to `sync` when Run a Google Calendar action. An explicit action is required.
omitted.
Actions: ~ Actions: ~
`sync` Push tasks with due dates to Google Calendar (default). `push` Push tasks with due dates to Google Calendar.
`auth` Run the OAuth authorization flow. `auth` Run the OAuth authorization flow.
Examples: >vim Examples: >vim
:Pending gcal " push to Google Calendar (default) :Pending gcal push " push to Google Calendar
:Pending gcal auth " authorize :Pending gcal auth " authorize
< <
@ -606,9 +605,7 @@ loads: >lua
prev_task = '[t', prev_task = '[t',
}, },
sync = { sync = {
gcal = { gcal = {},
calendar = 'Pendings',
},
gtasks = {}, gtasks = {},
}, },
} }
@ -893,8 +890,8 @@ SYNC BACKENDS *pending-sync-backend*
Sync backends are Lua modules under `lua/pending/sync/<name>.lua`. Each Sync backends are Lua modules under `lua/pending/sync/<name>.lua`. Each
backend is exposed as a top-level `:Pending` subcommand: >vim backend is exposed as a top-level `:Pending` subcommand: >vim
:Pending gtasks [action] :Pending gtasks {action}
:Pending gcal [action] :Pending gcal {action}
< <
Each module returns a table conforming to the backend interface: >lua Each module returns a table conforming to the backend interface: >lua
@ -902,9 +899,9 @@ Each module returns a table conforming to the backend interface: >lua
---@class pending.SyncBackend ---@class pending.SyncBackend
---@field name string ---@field name string
---@field auth fun(): nil ---@field auth fun(): nil
---@field sync fun(): nil
---@field push? fun(): nil ---@field push? fun(): nil
---@field pull? fun(): nil ---@field pull? fun(): nil
---@field sync? fun(): nil
---@field health? fun(): nil ---@field health? fun(): nil
< <
@ -924,16 +921,15 @@ Backend-specific configuration goes under `sync.<name>` in |pending-config|.
============================================================================== ==============================================================================
GOOGLE CALENDAR *pending-gcal* GOOGLE CALENDAR *pending-gcal*
pending.nvim can push tasks with due dates to a dedicated Google Calendar as pending.nvim can push tasks with due dates to Google Calendar as all-day
all-day events. This is a one-way push; changes made in Google Calendar are events. Each pending.nvim category maps to a Google Calendar of the same
not pulled back into pending.nvim. name. Calendars are created automatically on first push. This is a one-way
push; changes made in Google Calendar are not pulled back.
Configuration: >lua Configuration: >lua
vim.g.pending = { vim.g.pending = {
sync = { sync = {
gcal = { gcal = {},
calendar = 'Pendings',
},
}, },
} }
< <
@ -943,11 +939,6 @@ used by default. Run `:Pending gcal auth` and the browser opens immediately.
*pending.GcalConfig* *pending.GcalConfig*
Fields: ~ Fields: ~
{calendar} (string, default: 'Pendings')
Name of the Google Calendar to sync to. If a calendar
with this name does not exist it is created
automatically on the first sync.
{client_id} (string, optional) {client_id} (string, optional)
OAuth client ID. When set together with OAuth client ID. When set together with
{client_secret}, these take priority over the {client_secret}, these take priority over the
@ -973,26 +964,24 @@ OAuth flow: ~
On the first `:Pending gcal` call the plugin detects that no refresh token On the first `:Pending gcal` call the plugin detects that no refresh token
exists and opens the Google authorization URL in the browser using exists and opens the Google authorization URL in the browser using
|vim.ui.open()|. A temporary local HTTP server listens on port 18392 for the |vim.ui.open()|. A temporary local HTTP server listens on port 18392 for the
OAuth redirect. The PKCE (Proof Key for Code Exchange) flow is used OAuth redirect. The PKCE (Proof Key for Code Exchange) flow is used. After
`openssl` generates the code challenge. After the user grants consent, the the user grants consent, the
authorization code is exchanged for tokens and the refresh token is stored at authorization code is exchanged for tokens and the refresh token is stored at
`stdpath('data')/pending/gcal_tokens.json` with mode `600`. Subsequent syncs `stdpath('data')/pending/gcal_tokens.json` with mode `600`. Subsequent syncs
use the stored refresh token and refresh the access token automatically when use the stored refresh token and refresh the access token automatically when
it is about to expire. it is about to expire.
`:Pending gcal` behavior: ~ `:Pending gcal push` behavior: ~
For each task in the store: For each task in the store:
- A pending task with a due date and no existing event: a new all-day event is - A pending task with a due date and no existing event: a new all-day event is
created and the event ID is stored in the task's `_extra` table. created in the calendar matching the task's category. The event ID and
calendar ID are stored in the task's `_extra` table.
- A pending task with a due date and an existing event: the event summary and - A pending task with a due date and an existing event: the event summary and
date are updated in place. date are updated in place.
- A done or deleted task with an existing event: the event is deleted. - A done or deleted task with an existing event: the event is deleted.
- A pending task with no due date that had an existing event: the event is - A pending task with no due date that had an existing event: the event is
deleted. deleted.
A summary notification is shown after sync: `created: N, updated: N,
deleted: N`.
============================================================================== ==============================================================================
GOOGLE TASKS *pending-gtasks* GOOGLE TASKS *pending-gtasks*

View file

@ -1,30 +0,0 @@
vim.opt.runtimepath:prepend(vim.fn.getcwd())
local tmpdir = vim.fn.tempname()
vim.fn.mkdir(tmpdir, 'p')
vim.g.pending = {
data_path = tmpdir .. '/tasks.json',
}
local store = require('pending.store')
store.load()
local today = os.date('%Y-%m-%d')
local yesterday = os.date('%Y-%m-%d', os.time() - 86400)
local tomorrow = os.date('%Y-%m-%d', os.time() + 86400)
store.add({
description = 'Finish quarterly report',
category = 'Work',
due = tomorrow,
recur = 'monthly',
priority = 1,
})
store.add({ description = 'Review pull requests', category = 'Work' })
store.add({ description = 'Update deployment docs', category = 'Work', status = 'done' })
store.add({ description = 'Buy groceries', category = 'Personal', due = today })
store.add({ description = 'Call dentist', category = 'Personal', due = yesterday, priority = 1 })
store.add({ description = 'Read chapter 5', category = 'Personal' })
store.add({ description = 'Learn a new language', category = 'Someday' })
store.add({ description = 'Plan hiking trip', category = 'Someday' })
store.save()

View file

@ -1,28 +0,0 @@
Output assets/demo.gif
Require nvim
Set Shell "bash"
Set FontSize 14
Set Width 900
Set Height 450
Type "nvim -u scripts/demo-init.lua -c 'autocmd VimEnter * Pending'"
Enter
Sleep 2s
Down
Down
Sleep 300ms
Down
Sleep 300ms
Enter
Sleep 500ms
Tab
Sleep 1s
Type "q"
Sleep 200ms

View file

@ -49,27 +49,27 @@ describe('sync', function()
assert.are.equal("gcal backend has no 'notreal' action", msg) assert.are.equal("gcal backend has no 'notreal' action", msg)
end) end)
it('defaults to sync action when action is omitted', function() it('lists actions when action is omitted', function()
local called = false local msg = nil
local gcal = require('pending.sync.gcal') local orig = vim.notify
local orig_sync = gcal.sync vim.notify = function(m)
gcal.sync = function() msg = m
called = true
end end
pending.command('gcal') pending.command('gcal')
gcal.sync = orig_sync vim.notify = orig
assert.is_true(called) assert.is_not_nil(msg)
assert.is_truthy(msg:find('push'))
end) end)
it('routes explicit sync action', function() it('routes explicit push action', function()
local called = false local called = false
local gcal = require('pending.sync.gcal') local gcal = require('pending.sync.gcal')
local orig_sync = gcal.sync local orig_push = gcal.push
gcal.sync = function() gcal.push = function()
called = true called = true
end end
pending.command('gcal sync') pending.command('gcal push')
gcal.sync = orig_sync gcal.push = orig_push
assert.is_true(called) assert.is_true(called)
end) end)
@ -90,10 +90,10 @@ describe('sync', function()
config.reset() config.reset()
vim.g.pending = { vim.g.pending = {
data_path = tmpdir .. '/tasks.json', data_path = tmpdir .. '/tasks.json',
sync = { gcal = { calendar = 'NewStyle' } }, sync = { gcal = { client_id = 'test-id' } },
} }
local cfg = config.get() local cfg = config.get()
assert.are.equal('NewStyle', cfg.sync.gcal.calendar) assert.are.equal('test-id', cfg.sync.gcal.client_id)
end) end)
describe('gcal module', function() describe('gcal module', function()
@ -107,9 +107,9 @@ describe('sync', function()
assert.are.equal('function', type(gcal.auth)) assert.are.equal('function', type(gcal.auth))
end) end)
it('has sync function', function() it('has push function', function()
local gcal = require('pending.sync.gcal') local gcal = require('pending.sync.gcal')
assert.are.equal('function', type(gcal.sync)) assert.are.equal('function', type(gcal.push))
end) end)
it('has health function', function() it('has health function', function()