feat: overdue highlighting, relative dates, undo write, buffer mappings (#1)

* feat(config): add category_order field

Problem: category display order was always insertion order with no way
to configure it.

Solution: add category_order to config defaults so users can declare a
preferred category ordering; unspecified categories append after.

* feat(parse): add relative date resolution

Problem: due dates required full YYYY-MM-DD input, adding friction for
common cases like "today" or "next monday".

Solution: add resolve_date() supporting today, tomorrow, +Nd, and
weekday abbreviations; extend inline token parsing to resolve relative
values before falling back to strict date validation.

* feat(views): overdue flag, category in priority view, category ordering

Problem: overdue tasks were visually indistinct from upcoming ones;
priority view had no category context; category display order was not
configurable.

Solution: compute overdue meta flag for pending tasks past their due
date; set show_category on priority view task meta; reorder categories
according to config.category_order when present.

* feat(buffer): overdue highlight, category virt text in priority view

Problem: overdue tasks had no visual distinction; priority view showed
no category context alongside due dates.

Solution: add PendingOverdue highlight group; render category name as
right-aligned virtual text in priority view, composited with the due
date when both are present.

* feat(init): undo write and buffer-local default mappings

Problem: _undo_state was captured on every save but never consumed;
toggle_priority and prompt_date had no buffer-local defaults, requiring
manual <Plug> configuration.

Solution: implement undo_write() to restore pre-save task state; add !,
d, and U as buffer-local defaults following fugitive's philosophy of
owning the buffer; expose :Pending undo as a command alias.

* test(views): add views spec

Problem: views.lua had no test coverage.

Solution: add 26 tests covering category_view and priority_view
including sort order, line format, overdue detection, show_category
meta, and category_order config behavior.

* test(archive): add archive spec

Problem: archive had no test coverage.

Solution: add 9 tests covering cutoff logic, custom day counts, pending
task preservation, deleted task cleanup, and notify output.

* docs: add vimdoc

Problem: no :help documentation existed.

Solution: add doc/pending.txt covering all features — commands,
mappings, views, configuration, Google Calendar sync, highlight groups,
data format, and health check — following standard vimdoc conventions.

* ci: format

* fix: resolve lint and type check errors

Problem: selene flagged unused variables in new spec files; LuaLS
flagged os.date/os.time return type mismatches, integer? assignments,
and stale task.Task/task.GcalConfig type references.

Solution: prefix unused spec variables with _ or drop unnecessary
assignments; add --[[@as string/integer]] casts for os.date and
os.time calls; add category_order field to pending.Config annotation;
fix task.GcalConfig -> pending.GcalConfig and task.Task[] ->
pending.Task[]; add nil guards on meta[row].id before store calls;
cast store.data() return to non-optional.

* ci: format

* fix: sync

* ci: format
This commit is contained in:
Barrett Ruth 2026-02-24 18:33:07 -05:00
parent 7b97e9b840
commit 3a35fab6cf
11 changed files with 1137 additions and 30 deletions

View file

@ -313,7 +313,7 @@ local function find_or_create_calendar(access_token)
return nil, err
end
for _, item in ipairs(data.items or {}) do
for _, item in ipairs(data and data.items or {}) do
if item.summary == cal_name then
return item.id, nil
end
@ -326,12 +326,13 @@ local function find_or_create_calendar(access_token)
return nil, create_err
end
return created.id, nil
return created and created.id, nil
end
local function next_day(date_str)
local y, m, d = date_str:match('^(%d%d%d%d)-(%d%d)-(%d%d)$')
local t = os.time({ year = tonumber(y), month = tonumber(m), day = tonumber(d) }) + 86400
local t = os.time({ year = tonumber(y) or 0, month = tonumber(m) or 0, day = tonumber(d) or 0 })
+ 86400
return os.date('%Y-%m-%d', t)
end
@ -354,7 +355,7 @@ local function create_event(access_token, calendar_id, task)
if err then
return nil, err
end
return data.id, nil
return data and data.id, nil
end
local function update_event(access_token, calendar_id, event_id, task)
@ -416,7 +417,7 @@ function M.sync()
else
task._extra = extra
end
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ')
task.modified = tostring(os.date('!%Y-%m-%dT%H:%M:%SZ'))
deleted = deleted + 1
end
elseif task.status == 'pending' and task.due then
@ -432,7 +433,7 @@ function M.sync()
task._extra = {}
end
task._extra._gcal_event_id = new_id
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ')
task.modified = tostring(os.date('!%Y-%m-%dT%H:%M:%SZ'))
created = created + 1
end
end