Problem: setup_indentexpr always returned 0 because no task line
starts with whitespace (the /ID/ prefix begins with /), so the
return 2 branch was dead code. Pressing o or O opened a blank line
at column 0 with no ID prefix, which the diff parser cannot
recognise as a task.
Solution: remove setup_indentexpr and M.get_indent() entirely; add
M.open_line(above) which inserts a two-space stub line and enters
insert mode at the end so the user types directly into the new task
body. The diff layer already handles lines matching ^ .+ as new
tasks. Add o and O buffer-local mappings in init.lua.
Problem: pressing ! re-sorts the view so the toggled task moves to
the top of its category, but the cursor stays on the original line
number and lands on a different task.
Solution: after buffer.render(), iterate buffer.meta() to find the
new line number for the toggled task's id and call
nvim_win_set_cursor to follow it.
Problem: highlight groups used hardcoded hex colours and a bespoke
hlexists guard, ignoring the user's colorscheme and preventing
overrides from working naturally.
Solution: replace the guard wrapper with direct nvim_set_hl calls
using default = true and link, so each group falls back to a
semantically appropriate built-in group (Title, DiagnosticHint,
DiagnosticError, Comment, DiagnosticWarn) unless the user has
already defined them.
Problem: mapping d for the date prompt intercepts dd before Vim can
recognize it as a motion, so dd never deletes a line.
Solution: move the date prompt to D, restoring full d-operator
behaviour (dd, dw, d$, etc.) and updating the help popup to match.
Problem: CI lua-typecheck-action reported three categories of errors:
1. parse.lua - multi-assignment of tonumber() results left y/m/d typed
as number? rather than integer, failing os.time()'s field types
2. gcal.lua - url_encode returned str:gsub() which yields string+integer
but the annotation declared @return string (redundant-return-value)
3. gcal.lua - calendar_id typed string? from find_or_create_calendar was
passed to functions expecting string; the existing `if err` guard did
not narrow the type for LuaLS
Solution: replace the y/m/d multi-assignment with yn/mn/dn locals whose
types resolve cleanly to integer; wrap the gsub return in parentheses to
discard the count; add `or not calendar_id` to the error guard so LuaLS
narrows calendar_id to string for the rest of the scope.
Problem: undo was single-level with shallow references; no way to
query due/overdue tasks via quickfix; two instances sharing
tasks.json would diverge silently.
Solution: replace _undo_state with _undo_states[] (cap 20, deep
copies via store.snapshot()); add M.due() which populates the
quickfix list with overdue/due-today tasks; add BufEnter autocmd
that reloads from disk when the buffer is unmodified; expand
show_help() with folds, :Pending due, relative date syntax,
PendingOverdue, and empty-input date clearing.
Problem: category_view had no fold support, making it harder to
focus on one category in large lists.
Solution: add M.get_fold() returning '>1' for headers, '1' for task
lines, and '0' for blanks. M.render() now sets foldmethod=expr
(foldlevel=99) in category view and foldmethod=manual in priority.
Problem: the single-level undo used shallow references so mutations
during diff.apply() corrupted the saved state. JSON writes were also
non-atomic, risking partial writes on crash.
Solution: add M.snapshot() which deep-copies active tasks (including
_extra). Change M.save() to write a .tmp file then rename atomically.
Problem: gcal.lua had ~10 LuaLS errors from untyped credential and
token tables, string|osdate casts, and untyped _gcal_event_id
field access.
Solution: add pending.GcalCredentials and pending.GcalTokens class
definitions, annotate all local functions with @param/@return, add
--[[@as string]] casts on os.date returns, and fix _gcal_event_id
access to use bracket notation with casts.
Problem: LuaLS types os.date('*t') as string|osdate, causing type
errors when accessing .year, .month, .day, .wday fields in
is_valid_date and resolve_date.
Solution: add --[[@as osdate]] casts on both os.date('*t') calls.
* 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
Problem: repo had no documentation for users.
Solution: add README covering usage, configuration, inline
metadata syntax, Google Calendar sync, mappings, and data
format.
Problem: rockspec file was named 'rockspec' instead of the
luarocks-required '<package>-<version>.rockspec' format.
Solution: rename to todo.nvim-scm-1.rockspec.
Problem: unused variables (undo_state, sha_result, date match
captures) and duplicate if/else branches in gcal sync triggered
selene warnings and errors.
Solution: prefix unused state with underscore, simplify date
validation to a single pattern match, remove dead sha_result
call, and merge duplicate event deletion branches into a single
condition.
Problem: parse_buffer classified /id/-prefixed task lines as headers
because '/' matches the '^%S' header pattern. Store timestamp test
was flaky when add and update ran within the same second.
Solution: check for task line patterns (id prefix or 2-space indent)
before falling through to the header branch. Backdate the initial
modified timestamp in the store update test.
Problem: need test coverage for core data operations, inline
metadata parsing, and buffer diff algorithm.
Solution: add busted specs for store CRUD, round-trip
preservation, parse body/command_add with configurable date
syntax, and diff create/delete/update/copy/move operations.
Problem: need a way to diagnose config, data file, and
dependency issues.
Solution: add health module reporting config values, data
directory status, task count, and curl/openssl availability.
Problem: need to push tasks with due dates to Google Calendar
as all-day events.
Solution: add gcal module with OAuth 2.0 loopback flow, PKCE,
token refresh, calendar creation, and event CRUD. Maps pending
tasks to all-day events, deletes events on completion, and
stores event IDs in task _extra for round-trip.
Problem: need user-facing :Todo command, buffer-local keymaps,
Plug mappings, completion toggle, help float, archive, and
syntax highlighting.
Solution: add init.lua with command dispatcher, toggle complete/
priority, date prompt, archive purge, and help float. Add
plugin/todo.lua entry point with :Todo command and Plug mappings.
Add syntax/todo.vim for conceal and priority highlighting.
Problem: need to reconcile buffer edits against the JSON store
on :w, handling creates, deletes, updates, reorders, and
duplicate IDs from yank/paste.
Solution: add diff module that parses buffer lines, matches
against stored tasks by ID, creates new tasks for unknown or
duplicate IDs, marks removed tasks as deleted, and updates
changed fields.
Problem: need a buffer to display tasks with concealed IDs,
virtual text due dates, strikethrough for done items, and
auto-indent.
Solution: add buffer module with acwrite scratch buffer, syntax
conceal for /id/ prefixes, right-aligned due date extmarks,
header/done highlighting, indentexpr, and view toggling.
Problem: need to render task lists grouped by category or sorted
by priority with concealed IDs and metadata.
Solution: add category_view and priority_view functions that
produce buffer lines with hidden /id/ prefixes, priority flags,
and line metadata for extmark placement.
Problem: need to extract due dates and categories from task
descriptions typed in the buffer.
Solution: add right-to-left token parser for configurable date
syntax (default 'due:') and cat: metadata keys. Supports
Category: prefix syntax for :Todo add commands.
Problem: need persistent task storage with forward-compatible
schema and unknown field preservation.
Solution: add store module with load/save, add/update/delete,
ID allocation, and UDA round-trip preservation. Data stored at
stdpath('data')/todo/tasks.json with version 1 schema.
Problem: need user-configurable settings with sensible defaults.
Solution: add config module that merges vim.g.todo with defaults
for data_path, default_view, default_category, date_format, and
date_syntax.